mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-20 03:35:41 +02:00
Compare commits
768 commits
Author | SHA1 | Date | |
---|---|---|---|
|
43e1944607 | ||
|
ca0f565237 | ||
|
dfa0970b75 | ||
|
dcbb0f88ae | ||
|
6a97b364a0 | ||
|
87c74d54b7 | ||
|
0bcde5c453 | ||
|
56467d0ba3 | ||
|
7e76d53c0a | ||
|
dcf936fd9b | ||
|
e423776000 | ||
|
e68ed8d843 | ||
|
c27de56bea | ||
|
b5450c3918 | ||
|
5a515eae99 | ||
|
b486088f77 | ||
|
a242d3d569 | ||
|
2eb1cda733 | ||
|
21f374d2eb | ||
|
fc24ea225d | ||
|
e1f7c0292f | ||
|
d746d12f6a | ||
|
61d082c802 | ||
|
57711f0dbe | ||
|
b15714b38a | ||
|
3e0083c9f2 | ||
|
a367895dc6 | ||
|
650d1f13f9 | ||
|
e1b6a78f22 | ||
|
20d48a753b | ||
|
37ecc251a4 | ||
|
4826a52306 | ||
|
0f47cbb889 | ||
|
c3f7fe013d | ||
|
b81e41c3c4 | ||
|
060586dbbe | ||
|
c80bae7604 | ||
|
311c481bfe | ||
|
9498d7e445 | ||
|
2d651d1c0e | ||
|
1b2dee55ef | ||
|
700bf5b28c | ||
|
f8ba887dcd | ||
|
887e770202 | ||
|
54902f46ab | ||
|
699c563051 | ||
|
a5ae61b5df | ||
|
568eb1140f | ||
|
3e0a65f185 | ||
|
1a323c6d21 | ||
|
739dc30323 | ||
|
8422e7e0b1 | ||
|
9cc5e0f7a7 | ||
|
3431d5fc75 | ||
|
20659d3350 | ||
|
0bea49b75e | ||
|
70efd7d15c | ||
|
b8a93a2673 | ||
|
a467f56677 | ||
|
b3313cefc6 | ||
|
00234696fe | ||
|
3a7455913f | ||
|
547cef5afb | ||
|
6f3952819f | ||
|
f148c68736 | ||
|
a2d30b96fb | ||
|
cae07a36ff | ||
|
3136310ade | ||
|
be01eeb1de | ||
|
13f46a314a | ||
|
3c572c70c9 | ||
|
b85ba99980 | ||
|
d841aeeecd | ||
|
28a18ad91e | ||
|
da19c09122 | ||
|
58c397d154 | ||
|
53dec73ed2 | ||
|
c44c66c83f | ||
|
4e07b63cef | ||
|
c19a31d925 | ||
|
4066326614 | ||
|
424f22ab84 | ||
|
7a5cf26fb5 | ||
|
89ae7bd743 | ||
|
f2d25c8341 | ||
|
aeeef4f236 | ||
|
195ac5d1cd | ||
|
9d90848291 | ||
|
bbbf2b601e | ||
|
cb2561a72c | ||
|
8b1fa13686 | ||
|
bdc4fbe8e7 | ||
|
e1f78a16ab | ||
|
26bf62a899 | ||
|
cdee55afed | ||
|
9365580539 | ||
|
3a3a711b69 | ||
|
4d46f25854 | ||
|
a943def94e | ||
|
e54e8635e0 | ||
|
176ed4a6f3 | ||
|
d5823bcc4c | ||
|
1283160e17 | ||
|
60671da2ca | ||
![]() |
14550440dd | ||
|
89e74139f5 | ||
|
cbd3bebb04 | ||
|
7fbc1f2c44 | ||
|
9a111a52f5 | ||
|
78f7b60e13 | ||
|
9f5f35a8ac | ||
|
42cef9373e | ||
|
e1fc3a0e29 | ||
|
5db61745a4 | ||
|
4ba193ad0f | ||
|
c4e094de3e | ||
|
1b23e72770 | ||
|
5ac7d51e8a | ||
|
f923261fec | ||
|
8e4d7f04e4 | ||
|
d6e7710a7e | ||
|
f948b9f8f9 | ||
|
6220a07aaf | ||
|
a342e036ad | ||
|
b694fc1583 | ||
|
08f5f444eb | ||
|
d236b9c1b9 | ||
|
7c5ea4fed6 | ||
|
9218ef234c | ||
|
971361b046 | ||
|
5e3859f218 | ||
|
963b9d47ee | ||
|
daeb59e021 | ||
|
3ec6fa1bc7 | ||
|
f21db9caca | ||
|
8ccd79ad08 | ||
|
d1776714ed | ||
|
5da51210de | ||
|
7773a17d57 | ||
|
bdbcc0100a | ||
|
10fde4dd0a | ||
|
134ae847dc | ||
|
f75168796a | ||
|
d09d88b60b | ||
|
0f3894bf63 | ||
|
38a1d0b57c | ||
|
11bef7dd08 | ||
|
146759bd96 | ||
|
73ccafdade | ||
|
02d281df58 | ||
|
e4edbd26c6 | ||
|
310c07b03d | ||
|
2283647fc7 | ||
|
d26d3953f1 | ||
|
c4f820e486 | ||
|
500b051fe4 | ||
|
8fbbce10a5 | ||
|
ac8e45c331 | ||
|
a18296a179 | ||
|
1f25978eb4 | ||
|
c0e0702a6c | ||
|
23d47656e4 | ||
|
4318765030 | ||
|
cb8acf261a | ||
|
63c9c90a61 | ||
|
252a7a1580 | ||
|
710cede371 | ||
|
ede6a541e1 | ||
|
a9ce81b376 | ||
|
a599e7264f | ||
|
1353d635c2 | ||
|
ef7b4ce9b3 | ||
|
06bf127332 | ||
|
b195bc4dcb | ||
|
2e0e1a402f | ||
|
3ca274759a | ||
|
6794193791 | ||
|
95b1f5f261 | ||
|
b22614ecc3 | ||
|
c4cc1b7a36 | ||
|
a53e48a2c1 | ||
|
a5e79d14b7 | ||
|
690bd630a2 | ||
|
9ef6d73663 | ||
|
56b0047004 | ||
|
1a81255579 | ||
|
a14d38b0cb | ||
|
b6450446a8 | ||
|
0cf0d64970 | ||
|
ec9ed66b6b | ||
|
eb26f64ea7 | ||
|
b901ac50ee | ||
|
8d5e8b5f20 | ||
|
f54f583be1 | ||
|
85d55905f9 | ||
|
659b282445 | ||
|
b23365ccac | ||
|
25e123fbe6 | ||
|
aeef3eca0e | ||
|
881359183f | ||
|
4c1398f1a5 | ||
|
f8f0d7ae99 | ||
|
49576a26bf | ||
|
9b93b0794a | ||
|
266a2efbb6 | ||
|
24a3b90a01 | ||
|
8f89545b32 | ||
|
bbd2394601 | ||
|
6fa9c47c0b | ||
|
7ddd009a5c | ||
|
4631e75e28 | ||
|
f5cfc103d0 | ||
|
6027b2728b | ||
|
bd607d7697 | ||
|
302e0d5cc6 | ||
|
19a9f099e2 | ||
|
dcf21f0b06 | ||
|
54c70bb6ad | ||
|
f9fa43845e | ||
|
ec86a7d290 | ||
|
3c3a881638 | ||
|
4a41d4296a | ||
|
463b39b56d | ||
|
87854fa101 | ||
|
8deac539ef | ||
|
794b1ed633 | ||
|
8f1a123de2 | ||
|
028011a816 | ||
|
6ed576c719 | ||
|
8d5deda4e4 | ||
|
4143099e94 | ||
|
d1a8029e6c | ||
|
5da1cd4a38 | ||
|
d002919bad | ||
|
6427e8213c | ||
|
aa09d88d8e | ||
|
2759ba6349 | ||
|
4257c80eee | ||
|
084a9021b9 | ||
|
b331473a6b | ||
|
d15d1f58f7 | ||
|
138db05d70 | ||
|
dd1280f49d | ||
|
8d91cbd8a3 | ||
|
6c10eb2153 | ||
|
a0c07d7836 | ||
|
cb47c53de4 | ||
|
605a6a9ede | ||
|
1b03bd6bc0 | ||
|
36a4250a96 | ||
|
b0e132beaf | ||
|
03e1c7dc13 | ||
|
eb5cb9869c | ||
|
9353aa14fe | ||
|
a8994b9268 | ||
|
8a8a40bfb5 | ||
|
f4ceaaad52 | ||
|
ca077447c2 | ||
|
d9d5671e04 | ||
|
de4814d16e | ||
|
c738f1c63d | ||
|
a3a0334069 | ||
|
5fc092a874 | ||
|
acc20913ef | ||
|
3b5845370c | ||
|
82a3b2ae11 | ||
|
0d878e8b5c | ||
|
4c4a20d835 | ||
|
2b103b7acd | ||
|
4496d82cfb | ||
|
1206153b03 | ||
|
acb8a09376 | ||
|
c1e549df54 | ||
|
9c28d36898 | ||
|
62ca06eccb | ||
|
fce2787bdf | ||
|
43de7c8da8 | ||
|
fd014dc33b | ||
|
068c25d8f6 | ||
|
2b6f5b1e36 | ||
|
4bb81e8940 | ||
|
3ff1c95208 | ||
|
4daa3d9904 | ||
|
ffccabbb13 | ||
|
265188ca4c | ||
|
c44970717b | ||
|
1ce108f24e | ||
|
ca407cd166 | ||
|
a2cf05a64d | ||
|
605490c872 | ||
|
6ac046dec3 | ||
|
2cfe45ee81 | ||
|
1fc6e77926 | ||
|
3cc142a273 | ||
|
5edc226c00 | ||
|
973aa929c1 | ||
|
b8ab8669cb | ||
|
227f9c608a | ||
|
ff238e62ba | ||
|
4fea561c6c | ||
|
52e0b5f3d1 | ||
|
1cf14a7f80 | ||
|
7945a561d0 | ||
|
1d9297593e | ||
|
a59d3cfbd6 | ||
|
f053ddff7d | ||
|
56c4a1c751 | ||
|
2a0a722c13 | ||
|
37e7c2b2c1 | ||
|
0aff641d0c | ||
|
52e2540d42 | ||
|
d9316a202d | ||
|
4ff1c43669 | ||
|
e83c4bd8c1 | ||
|
ae5c7e0fc3 | ||
|
337ce7681f | ||
|
d8d44b0f33 | ||
|
0af9ce354b | ||
|
f7206ef08d | ||
|
375c7ca89c | ||
|
26ea137938 | ||
|
8475ca1603 | ||
|
3e133d8618 | ||
|
1bb77e59f3 | ||
|
9cff50a39e | ||
|
96c75b7f73 | ||
|
ffa86d84a5 | ||
|
5d09e59f11 | ||
|
8a11a3fbe5 | ||
|
cdd0b5b4f0 | ||
|
82ef48f666 | ||
|
f922973450 | ||
|
6612a9af9f | ||
|
f71e7c2905 | ||
|
39a5493450 | ||
|
b562f1310b | ||
|
9f2197ca1c | ||
|
9d94cec549 | ||
|
4f0d27bc7e | ||
|
f166bbbf54 | ||
|
b6931c6ed0 | ||
|
23f12a65b2 | ||
|
72056c50cf | ||
|
11bb45aa87 | ||
|
d40220e511 | ||
|
58038a4236 | ||
|
8324112504 | ||
|
66ec7a85a1 | ||
|
0f6d165084 | ||
|
134141b7c5 | ||
|
f0782d5124 | ||
|
c9abb6b98f | ||
|
31f160141e | ||
|
9ffd305b59 | ||
|
4e96dbd7f7 | ||
|
7d715d81a8 | ||
|
cad9dd8efd | ||
|
bd44e82eca | ||
|
939b81a4ea | ||
|
fee0b91174 | ||
|
4f3aa8c6b0 | ||
|
957e25914c | ||
|
2b670ea612 | ||
|
d7a58e4ee0 | ||
|
94a0154c23 | ||
|
515b36da0d | ||
|
9fe8ef2574 | ||
|
9a1371b96a | ||
|
6250360c58 | ||
|
77303e8173 | ||
|
4cd031bc73 | ||
|
03476e9360 | ||
|
ec465ba3b3 | ||
|
f8e544ae05 | ||
|
82c92185ea | ||
|
bfff39b7c9 | ||
|
804af178d5 | ||
|
6a9f66b837 | ||
|
2e6847077e | ||
|
26892c66b2 | ||
|
1bcf116859 | ||
|
0f389435ca | ||
|
fa760ffa65 | ||
|
76225a8366 | ||
|
87f38d19a0 | ||
|
535d49e9c3 | ||
|
8709e8da38 | ||
|
7bbcad55e4 | ||
|
185f313580 | ||
|
13b5934e65 | ||
|
e723b039ad | ||
|
12f7802537 | ||
|
ba5b28f437 | ||
|
60904bb18f | ||
|
8076a8da2a | ||
|
3ff9fcab48 | ||
|
dd19f070b3 | ||
|
ab0323e35e | ||
|
42d944d020 | ||
|
6fa118a5e5 | ||
|
7a5ce73c94 | ||
|
eff890ab9d | ||
|
8c2e5d8bde | ||
|
f0b16033fe | ||
|
a6194c63e6 | ||
|
e201cc3d30 | ||
|
0da24198b3 | ||
|
4ab3263ebb | ||
|
dabb2e1407 | ||
|
d450bf12a1 | ||
|
e8a2f8df9a | ||
|
066427d77a | ||
|
25379b7e1f | ||
|
01ee028c4d | ||
|
a685dadb75 | ||
|
b27eff36f9 | ||
|
9b3548736a | ||
|
5249d9ef79 | ||
|
d39e6b8b94 | ||
|
e4e9587322 | ||
|
e9d762fa03 | ||
|
149798fe98 | ||
|
1079bca3eb | ||
|
d1c7647b03 | ||
|
7c7c4e7ce9 | ||
|
b8dd247111 | ||
|
427e0ce418 | ||
|
7ca22a6dab | ||
|
af0b7e57d8 | ||
|
def90edde1 | ||
|
dab6428859 | ||
|
73e1d328c3 | ||
|
ca43eb3016 | ||
|
0abc0f37dd | ||
|
103c3102a9 | ||
|
621f0e18e6 | ||
|
9681e0aabe | ||
|
589a6f528a | ||
|
01c6fc5f52 | ||
|
fe6cc43ad8 | ||
|
2173e0dc4d | ||
|
560d7464b4 | ||
|
eb94c8cceb | ||
|
7e7c011126 | ||
|
0963afd282 | ||
|
36de95cc2a | ||
|
a5be550964 | ||
|
360e1fbada | ||
|
5af070ee1d | ||
|
25c20e5534 | ||
|
db12ceb026 | ||
|
ae7d54fb80 | ||
|
591cae4c6d | ||
|
be6e714eb0 | ||
|
8b6b82f1e5 | ||
|
b00954045b | ||
|
495a4c8fb1 | ||
|
be10465a3b | ||
|
7d3851046e | ||
|
58a52512dd | ||
|
910522262f | ||
|
315044d342 | ||
|
ba7b9e6244 | ||
|
1c6c73928b | ||
|
74016d7d33 | ||
|
96a35e5304 | ||
|
a210d33320 | ||
|
3072c2b13f | ||
|
64df5d9806 | ||
|
7d94631991 | ||
|
741107d31c | ||
|
b97ba80aea | ||
|
8c095eb423 | ||
|
30f2880be7 | ||
|
b4ce851b4d | ||
|
239ce16a79 | ||
|
7da13a26d0 | ||
|
21adc40a52 | ||
|
b1932872ce | ||
|
66ea64d826 | ||
|
481040f2da | ||
|
213974c796 | ||
|
e12b3af290 | ||
|
b0be7677ab | ||
|
8187d60193 | ||
|
71ecdb6789 | ||
|
0ddabacc77 | ||
|
cf41d008f8 | ||
|
e4a0b375e5 | ||
|
fc9c3ebbb8 | ||
|
e11fe12c98 | ||
|
0aef2f85ee | ||
|
4ce3fe2285 | ||
|
46e6539b1a | ||
|
530afe6cf5 | ||
|
93a5bbb4a4 | ||
|
5e6e9e189b | ||
|
4e2c4e1e3a | ||
|
13ef977eeb | ||
|
34d832cd22 | ||
|
c79ffbe057 | ||
|
af163d3f77 | ||
|
dd724d1bc2 | ||
|
8f7ef7c20b | ||
|
7c6874d826 | ||
|
f5903112cd | ||
|
08fa56a0f4 | ||
|
371bfb4065 | ||
|
ed2b8c4874 | ||
|
97d5570daf | ||
|
d0dd65cef5 | ||
|
60ee992a73 | ||
|
8fc0d148c8 | ||
|
e2f3df87a3 | ||
|
35e6943531 | ||
|
914cea1b14 | ||
|
aadb1b22b3 | ||
|
d95023adc8 | ||
|
8153e40f2a | ||
|
459ca5616d | ||
|
463f1ea75d | ||
|
85ce6dc8ef | ||
|
15ed0e043b | ||
|
0e9c96e6b3 | ||
|
64c53bab49 | ||
|
5c4ae642f2 | ||
|
cb45e53cb4 | ||
|
a5bbf0b769 | ||
|
18a0920ed9 | ||
|
f9dad99db8 | ||
|
db7a4af80a | ||
|
9a6f691493 | ||
|
8c93b48146 | ||
|
a3dd3916a8 | ||
|
21a84aed72 | ||
|
4efb7df7db | ||
|
f12db42112 | ||
|
0a60604a3f | ||
|
0edfd8e22c | ||
|
d69ca5a0c9 | ||
|
db34254677 | ||
|
46b222ed23 | ||
|
eb76bb4830 | ||
|
8920413e12 | ||
|
da0edab3fc | ||
|
646ad0b0eb | ||
|
ace0ebd062 | ||
|
1f1d68e9d7 | ||
|
075ddf3f50 | ||
|
db15c63c90 | ||
|
0f1c3548ae | ||
|
faa5f7f9f1 | ||
|
c4f58e8673 | ||
|
db6e868011 | ||
|
153d7a2ffa | ||
|
0855e5ff63 | ||
|
afe22813f3 | ||
|
cc6be3a923 | ||
|
98a4789e26 | ||
|
fe252a1410 | ||
|
264c051232 | ||
|
0bc0012c06 | ||
|
40a9e68b1e | ||
|
2563a233d7 | ||
|
686e57176e | ||
|
90339a00a5 | ||
|
524f93c997 | ||
|
8f3de369ac | ||
|
972395bf2e | ||
|
72660b3c01 | ||
|
2f66ac75ba | ||
|
ae403c842c | ||
|
d684dc0463 | ||
|
4f70346601 | ||
|
cfeb5260dd | ||
|
6cbf093af9 | ||
|
f88f7a5046 | ||
|
47c42b507f | ||
|
5b13b5315f | ||
|
536f9d22ec | ||
|
492efdd10e | ||
|
8afa17fa87 | ||
|
c14d5b3b9c | ||
|
1e4a282fb5 | ||
|
18e6f0e4bd | ||
|
891d9add5e | ||
|
7af5e56e4b | ||
|
5bfa104935 | ||
|
e85e3e8ced | ||
|
87b6b98695 | ||
|
86cc3f0918 | ||
|
3603ca982a | ||
|
e2c2f48203 | ||
|
77ca2e8225 | ||
|
b679e8ce9a | ||
|
761f2e0b21 | ||
|
9cf00f8200 | ||
|
3d8bdfadb4 | ||
|
547423556c | ||
|
d3512f41d7 | ||
|
3b4d822888 | ||
|
2262a3d837 | ||
|
9c03bd887f | ||
|
dee61e6239 | ||
|
946678c7ef | ||
|
85680ad5a8 | ||
|
105409b0e0 | ||
|
e7f1d3e6b5 | ||
|
e411553035 | ||
|
3da796810b | ||
|
d9496152e3 | ||
|
ab8cc7df47 | ||
|
20df360937 | ||
|
45d9dbcb34 | ||
|
8249b9c7ea | ||
|
d0f1f762ea | ||
|
d5fc1074d8 | ||
|
678d82e4d4 | ||
|
b9fda4482d | ||
|
c9ef44a775 | ||
|
96d2d057e0 | ||
|
4dba602bfd | ||
|
9718c6f31e | ||
|
4a9f550069 | ||
|
f49652130d | ||
|
220e43526c | ||
|
9d37697c4f | ||
|
f735bc5bd9 | ||
|
ae983b63c2 | ||
|
b1ee1ba403 | ||
|
d5a92cbf5f | ||
|
58e53b80a9 | ||
|
321d1cdc7d | ||
|
31c015c680 | ||
|
05aa44f1ab | ||
|
f438ad9b44 | ||
|
2fe602a6a2 | ||
|
ba54e709ee | ||
|
e0169d38f3 | ||
|
aa34925f54 | ||
|
31f6a4a6a0 | ||
|
c11a79c98d | ||
|
fef40d18e1 | ||
|
df2d8fec36 | ||
|
44db9304c5 | ||
|
c3cfae13e8 | ||
|
86ef9dcc02 | ||
|
198a351c7c | ||
|
d0360f2de1 | ||
|
074af015fb | ||
|
74754b0ab9 | ||
|
d10ad8924b | ||
|
f0a34d0055 | ||
|
1e5a1d0341 | ||
|
4d05947985 | ||
|
2bb70c6fcb | ||
|
5c9030129d | ||
|
37447cd955 | ||
|
9945fce2d2 | ||
|
7f1ffd126b | ||
|
fb0d443e1d | ||
|
008235d904 | ||
|
328ebe8fe9 | ||
|
73407853e4 | ||
|
08bac77907 | ||
|
8702378c74 | ||
|
c911d20e73 | ||
|
f2814f786e | ||
|
430e505bd2 | ||
|
fbaa208768 | ||
|
80d0025e64 | ||
|
99aa8dea82 | ||
|
19fe2f5a6f | ||
|
cadf227bc6 | ||
|
c0c5df0f6a | ||
|
8a7e07af28 | ||
|
1262f1b3d1 | ||
|
5113357fb2 | ||
|
fcfc7442c7 | ||
|
c55153bad8 | ||
|
d645aff48e | ||
|
e25c42dc87 | ||
|
41d0a17ea5 | ||
|
a5faf4896a | ||
|
bd29bf765b | ||
|
0cde404d2a | ||
|
318965b715 | ||
|
a041b8f971 | ||
|
1d579dff6b | ||
|
88f0b7b8c0 | ||
|
51e9dbd4fc | ||
|
db53cf9245 | ||
|
85ae4cca37 | ||
|
eceee99fb0 | ||
|
57e755477c | ||
|
f3db670455 | ||
|
6dfa6ec1db | ||
|
8d4e2e14a4 | ||
|
a2866aad0f | ||
|
c17e4b1110 | ||
|
a7820e1db7 | ||
|
06c2a69120 | ||
|
7b2d524598 | ||
|
e04b678518 | ||
|
d32698c042 | ||
|
771d090c18 | ||
|
588e6150d2 | ||
|
aa1b3457a1 | ||
|
e38f593acd | ||
|
74933c40ee | ||
|
84ba1c231a | ||
|
c03564513d | ||
|
0a0ef8852a | ||
|
f426155e0b | ||
|
6bf077240c | ||
|
dd74b7f747 | ||
|
31a4cddde3 | ||
|
d80fbd4084 | ||
|
d929c12ecb | ||
|
f04b1e806c | ||
|
5884e665a4 | ||
|
13c2b8296d | ||
|
40d47f8273 | ||
|
b658f577de | ||
|
b73e3acd0a | ||
|
6f22edaba6 | ||
|
6433373f93 | ||
|
a7af3764f0 | ||
|
cc8a54a0de | ||
|
3e0ca67a47 | ||
|
19d91dbb44 | ||
|
c28c970be6 | ||
|
87a8054ae2 | ||
|
5d17f1eb57 | ||
|
b0e9c02876 | ||
|
205801625f | ||
|
ae871853ca | ||
|
86e6aea152 | ||
|
ddd5911410 | ||
|
0fc2a29296 | ||
|
bdc5dea428 | ||
|
efb71cb5c5 | ||
|
0b1333aa03 | ||
|
1864d68961 | ||
|
50b4bf3783 | ||
|
70b528b088 | ||
|
cc9c5109e9 | ||
|
d108cce689 | ||
|
fb4db1f856 | ||
|
0e3bded42d | ||
|
bc392d8b0b | ||
|
44fb0b83ba | ||
|
0678015eb1 | ||
|
860ec01791 | ||
|
c707b00a2a | ||
|
c3b3d6a637 | ||
|
3fae6c2734 | ||
|
c7cfb451e3 | ||
|
11095201d7 | ||
|
6de185c242 | ||
|
bf7cd9a617 | ||
|
badf9b7622 | ||
|
234a787859 | ||
|
e5ef81a064 | ||
|
60a8bc7fb0 | ||
|
03a5c8746f | ||
|
b9e7417137 | ||
|
cdef89924c |
139 changed files with 19160 additions and 3842 deletions
|
@ -1,4 +1,4 @@
|
|||
image: alpine/edge
|
||||
image: alpine/latest
|
||||
packages:
|
||||
- musl-dev
|
||||
- eudev-libs
|
||||
|
@ -19,22 +19,28 @@ packages:
|
|||
- json-c-dev
|
||||
- libmpdclient-dev
|
||||
- alsa-lib-dev
|
||||
- pulseaudio-dev
|
||||
- pipewire-dev
|
||||
- ttf-dejavu
|
||||
- gcovr
|
||||
- python3
|
||||
- py3-pip
|
||||
- flex
|
||||
- bison
|
||||
|
||||
sources:
|
||||
- https://git.sr.ht/~dnkl/yambar
|
||||
|
||||
triggers:
|
||||
- action: email
|
||||
condition: failure
|
||||
to: daniel@ekloef.se
|
||||
# triggers:
|
||||
# - action: email
|
||||
# condition: failure
|
||||
# to: <comitter>
|
||||
|
||||
tasks:
|
||||
- install-gcovr: |
|
||||
python2 -m ensurepip --user --upgrade
|
||||
python2 -m pip install --user --upgrade pip
|
||||
python2 -m pip install --user --upgrade setuptools
|
||||
- fcft: |
|
||||
cd yambar/subprojects
|
||||
git clone https://codeberg.org/dnkl/fcft.git
|
||||
cd ../..
|
||||
- setup: |
|
||||
mkdir -p bld/debug bld/release bld/x11-only bld/wayland-only bld/plugs-are-shared
|
||||
meson --buildtype=debug -Db_coverage=true yambar bld/debug
|
24
.clang-format
Normal file
24
.clang-format
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
BasedOnStyle: GNU
|
||||
IndentWidth: 4
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Auto
|
||||
PointerAlignment: Right
|
||||
ColumnLimit: 120
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterEnum: false
|
||||
AfterClass: false
|
||||
SplitEmptyFunction: true
|
||||
AfterFunction: true
|
||||
AfterStruct: false
|
||||
|
||||
SpaceBeforeParens: ControlStatements
|
||||
Cpp11BracedListStyle: true
|
||||
|
||||
WhitespaceSensitiveMacros:
|
||||
- REGISTER_CORE_PARTICLE
|
||||
- REGISTER_CORE_DECORATION
|
||||
- REGISTER_CORE_PLUGIN
|
||||
- REGISTER_CORE_MODULE
|
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 70
|
||||
|
||||
[{meson.build,PKGBUILD}]
|
||||
indent_size = 2
|
||||
|
||||
[*.scd]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = false
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
/bld/
|
||||
/pkg/
|
||||
/src/
|
||||
/subprojects/
|
||||
/subprojects/*
|
||||
!/subprojects/*.wrap
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
image: alpine:edge
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
|
||||
before_script:
|
||||
- echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
|
||||
- apk update
|
||||
- apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc
|
||||
- apk add pixman-dev freetype-dev fontconfig-dev
|
||||
- apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev
|
||||
- apk add wayland-dev wayland-protocols wlroots-dev
|
||||
- apk add json-c-dev libmpdclient-dev alsa-lib-dev
|
||||
- apk add ttf-dejavu
|
||||
- apk add git
|
||||
- mkdir -p subprojects && cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/tllist.git
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
|
||||
debug:
|
||||
stage: build
|
||||
script:
|
||||
- apk add gcovr
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Db_coverage=true ../..
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ninja coverage-html
|
||||
- mv meson-logs/coveragereport ../../coverage
|
||||
- ninja coverage-text
|
||||
- tail -2 meson-logs/coverage.txt
|
||||
artifacts:
|
||||
paths:
|
||||
- coverage
|
||||
coverage: '/^TOTAL.*\s+(\d+\%)$/'
|
||||
|
||||
# valgrind:
|
||||
# stage: build
|
||||
# script:
|
||||
# - apk add valgrind
|
||||
# - mkdir -p bld/debug
|
||||
# - cd bld/debug
|
||||
# - meson --buildtype=debug ../..
|
||||
# - ninja -k0
|
||||
# - meson test --verbose --wrapper "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=3"
|
||||
|
||||
release:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/release
|
||||
- cd bld/release
|
||||
- meson --buildtype=minsize ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
x11_only:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
wayland_only:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
plugins_as_shared_modules:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
132
.woodpecker.yaml
Normal file
132
.woodpecker.yaml
Normal file
|
@ -0,0 +1,132 @@
|
|||
steps:
|
||||
- name: codespell
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk add openssl
|
||||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- python3 -m venv codespell-venv
|
||||
- source codespell-venv/bin/activate
|
||||
- pip install codespell
|
||||
- codespell README.md CHANGELOG.md *.c *.h doc/*.scd bar decorations modules particles examples
|
||||
- deactivate
|
||||
|
||||
- name: subprojects
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk add git
|
||||
- mkdir -p subprojects && cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/tllist.git
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
|
||||
- name: x64
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
depends_on: [subprojects]
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk update
|
||||
- apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc
|
||||
- apk add pixman-dev freetype-dev fontconfig-dev
|
||||
- apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev
|
||||
- apk add wayland-dev wayland-protocols wlroots-dev
|
||||
- apk add json-c-dev libmpdclient-dev alsa-lib-dev pulseaudio-dev pipewire-dev
|
||||
- apk add ttf-dejavu
|
||||
- apk add git
|
||||
- apk add flex bison
|
||||
|
||||
# Debug
|
||||
- apk add gcovr
|
||||
- mkdir -p bld/debug-x64
|
||||
- cd bld/debug-x64
|
||||
- meson --buildtype=debug -Db_coverage=true ../..
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ninja coverage-html
|
||||
- mv meson-logs/coveragereport ../../coverage
|
||||
- ninja coverage-text
|
||||
- tail -2 meson-logs/coverage.txt
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Release
|
||||
- mkdir -p bld/release-x64
|
||||
- cd bld/release-x64
|
||||
- meson --buildtype=minsize ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# X11 only
|
||||
- mkdir -p bld/x11-only
|
||||
- cd bld/x11-only
|
||||
- meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Wayland only
|
||||
- mkdir -p bld/wayland-only
|
||||
- cd bld/wayland-only
|
||||
- meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
- name: x86
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
depends_on: [subprojects]
|
||||
image: i386/alpine:latest
|
||||
commands:
|
||||
- apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc
|
||||
- apk add pixman-dev freetype-dev fontconfig-dev
|
||||
- apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev
|
||||
- apk add wayland-dev wayland-protocols wlroots-dev
|
||||
- apk add json-c-dev libmpdclient-dev alsa-lib-dev pulseaudio-dev pipewire-dev
|
||||
- apk add ttf-dejavu
|
||||
- apk add git
|
||||
- apk add flex bison
|
||||
|
||||
# Debug
|
||||
- mkdir -p bld/debug-x86
|
||||
- cd bld/debug-x86
|
||||
- meson --buildtype=debug ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Release
|
||||
- mkdir -p bld/release-x86
|
||||
- cd bld/release-x86
|
||||
- meson --buildtype=minsize ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Plugins as shared modules
|
||||
- mkdir -p bld/shared-modules
|
||||
- cd bld/shared-modules
|
||||
- meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
620
CHANGELOG.md
Normal file
620
CHANGELOG.md
Normal file
|
@ -0,0 +1,620 @@
|
|||
# Changelog
|
||||
|
||||
* [Unreleased](#unreleased)
|
||||
* [1.11.0](#1-11-0)
|
||||
* [1.10.0](#1-10-0)
|
||||
* [1.9.0](#1-9-0)
|
||||
* [1.8.0](#1-8-0)
|
||||
* [1.7.0](#1-7-0)
|
||||
* [1.6.2](#1-6-2)
|
||||
* [1.6.1](#1-6-1)
|
||||
* [1.6.0](#1-6-0)
|
||||
* [1.5.0](#1-5-0)
|
||||
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
|
||||
* environment variable substitution in config files ([#96][96]).
|
||||
* Log output now respects the [`NO_COLOR`](http://no-color.org/)
|
||||
environment variable.
|
||||
* network: `type` tag ([#380][380]).
|
||||
* network: `type` and `kind` tags ([#380][380]).
|
||||
* tags: `/<N>` tag formatter: divides the tag's decimal value with `N`
|
||||
([#392][392]).
|
||||
* i3/sway: `output` tag, reflecting the output (monitor) a workspace
|
||||
is on.
|
||||
* Added "string like" `~~` operator to Map particle. Allows glob-style
|
||||
matching on strings using `*` and `?` characters ([#400][400]).
|
||||
* Added "single" mode flag to the `mpd` module ([#428][428]).
|
||||
* niri: add a new module for niri-workspaces and niri-language
|
||||
([#404][404]).
|
||||
* pipewire: added `spacing`, `left-spacing` and `right-spacing`
|
||||
attributes.
|
||||
* mpris: new module ([#53][53]).
|
||||
|
||||
[96]: https://codeberg.org/dnkl/yambar/issues/96
|
||||
[380]: https://codeberg.org/dnkl/yambar/issues/380
|
||||
[392]: https://codeberg.org/dnkl/yambar/issues/392
|
||||
[400]: https://codeberg.org/dnkl/yambar/pulls/400
|
||||
[428]: https://codeberg.org/dnkl/yambar/pulls/428
|
||||
[404]: https://codeberg.org/dnkl/yambar/issues/404
|
||||
[53]: https://codeberg.org/dnkl/yambar/issues/53
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* `river`: expand to an empty list of particles when river is not
|
||||
running ([#384][384]).
|
||||
|
||||
[384]: https://codeberg.org/dnkl/yambar/issues/384
|
||||
|
||||
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
|
||||
* network: fix missing break in switch statement ([#377][377]).
|
||||
* i3/sway: crash when output is turned off an on ([#300][300]).
|
||||
* mpd: yambar never attempting to reconnect after MPD closed the
|
||||
connection (for example, when MPD is restarted).
|
||||
* Bar positioning on multi-monitor setups, when `location=bottom`.
|
||||
* pipewire: Improve handling of node switching ([#424][424]).
|
||||
|
||||
[377]: https://codeberg.org/dnkl/yambar/issues/377
|
||||
[300]: https://codeberg.org/dnkl/yambar/issues/300
|
||||
[424]: https://codeberg.org/dnkl/yambar/pulls/424
|
||||
|
||||
|
||||
### Security
|
||||
### Contributors
|
||||
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Added
|
||||
|
||||
* battery: current smoothing, for improved discharge estimates.
|
||||
* battery: scale option, for batteries that report 'charge' at a
|
||||
different scale than 'current'.
|
||||
* network: new `quality` tag (Wi-Fi only).
|
||||
* Read alternative config from pipes and FIFOs (e.g. `--config
|
||||
/dev/stdin`) ([#340][340]).
|
||||
* Added `overlay` and `background` as possible `layer` values
|
||||
([#372][372]).
|
||||
|
||||
[340]: https://codeberg.org/dnkl/yambar/pulls/340
|
||||
[372]: https://codeberg.org/dnkl/yambar/issues/372
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* log-level: default to `warning`
|
||||
* network: use dynlist instead of fixed name ([#355][355])
|
||||
|
||||
[355]: https://codeberg.org/dnkl/yambar/pulls/355
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Compiler error _‘fmt’ may be used uninitialized_ ([#311][311]).
|
||||
* map: conditions failing to match when they contain multiple, quoted
|
||||
tag values ([#302][302]).
|
||||
* Crash when hidden by an opaque window.
|
||||
* Bar not resizing itself when the screen resolution is changed
|
||||
([#330][330]).
|
||||
* i3/sway: incorrect empty/title state of workspaces ([#343][343]).
|
||||
* mem: state updated on each bar redraw ([#352][352]).
|
||||
* script: buffer overflow when reading large amounts of data.
|
||||
* i3/sway: module fails when reloading config file ([#361][361]).
|
||||
* Worked around bug in gcc causing a compilation error ([#350][350]).
|
||||
* Miscalculation of list width in presence of empty particles ([#369][369]).
|
||||
* Log-level not respected by syslog.
|
||||
|
||||
[311]: https://codeberg.org/dnkl/yambar/issues/311
|
||||
[302]: https://codeberg.org/dnkl/yambar/issues/302
|
||||
[330]: https://codeberg.org/dnkl/yambar/issues/330
|
||||
[343]: https://codeberg.org/dnkl/yambar/issues/343
|
||||
[352]: https://codeberg.org/dnkl/yambar/issues/352
|
||||
[361]: https://codeberg.org/dnkl/yambar/issues/361
|
||||
[350]: https://codeberg.org/dnkl/yambar/issues/350
|
||||
[369]: https://codeberg.org/dnkl/yambar/issues/369
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* Delgan
|
||||
* Haden Collins
|
||||
* Jordan Isaacs
|
||||
* kotyk
|
||||
* Leonardo Hernández Hernández
|
||||
* oob
|
||||
* rdbo
|
||||
* Sertonix
|
||||
* steovd
|
||||
* Väinö Mäkelä
|
||||
* Yiyu Zhou
|
||||
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### Added
|
||||
|
||||
* Field width tag format option ([#246][246])
|
||||
* river: support for ‘layout’ events.
|
||||
* dwl: support for specifying name of tags ([#256][256])
|
||||
* i3/sway: extend option `sort`; use `native` to sort numbered workspaces only.
|
||||
* modules/dwl: handle the appid status ([#284][284])
|
||||
* battery: also show estimation for time to full ([#303][303]).
|
||||
* on-click: tilde expansion ([#307][307])
|
||||
* script: tilde expansion of `path` ([#307][307]).
|
||||
|
||||
[246]: https://codeberg.org/dnkl/yambar/issues/246
|
||||
[256]: https://codeberg.org/dnkl/yambar/pulls/256
|
||||
[284]: https://codeberg.org/dnkl/yambar/pulls/284
|
||||
[307]: https://codeberg.org/dnkl/yambar/issues/307
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* disk-io: `interval` renamed to `poll-interval`
|
||||
* mem: `interval` renamed to `poll-interval`
|
||||
* battery/network/script: `poll-interval` unit changed from seconds to
|
||||
milliseconds ([#244][244]).
|
||||
* all modules: minimum poll interval changed from 500ms to 250ms.
|
||||
* network: do not use IPv6 link-local ([#281][281])
|
||||
|
||||
[244]: https://codeberg.org/dnkl/yambar/issues/244
|
||||
[281]: https://codeberg.org/dnkl/yambar/pulls/281
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Build failures for certain combinations of enabled and disabled
|
||||
plugins ([#239][239]).
|
||||
* Documentation for the `cpu` module; `interval` has been renamed to
|
||||
`poll-interval` ([#241][241]).
|
||||
* battery: module was not thread safe.
|
||||
* dwl module reporting only the last part of the title ([#251][251])
|
||||
* i3/sway: regression; persistent workspaces shown twice
|
||||
([#253][253]).
|
||||
* pipewire: use `roundf()` instead of `ceilf()` for more accuracy
|
||||
([#262][262])
|
||||
* Crash when a yaml anchor has a value that already exists in the
|
||||
target yaml node ([#286][286]).
|
||||
* battery: Fix time conversion in battery estimation ([#303][303]).
|
||||
* battery: poll timeout being reset when receiving irrelevant udev
|
||||
notification (leading to battery status never updating, in worst
|
||||
case) ([#305][305]).
|
||||
|
||||
[239]: https://codeberg.org/dnkl/yambar/issues/239
|
||||
[241]: https://codeberg.org/dnkl/yambar/issues/241
|
||||
[251]: https://codeberg.org/dnkl/yambar/pulls/251
|
||||
[253]: https://codeberg.org/dnkl/yambar/issues/253
|
||||
[262]: https://codeberg.org/dnkl/yambar/issues/262
|
||||
[286]: https://codeberg.org/dnkl/yambar/issues/286
|
||||
[305]: https://codeberg.org/dnkl/yambar/issues/305
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* Leonardo Gibrowski Faé (Horus)
|
||||
* Armin Fisslthaler
|
||||
* Ben Brown
|
||||
* David Bimmler
|
||||
* Leonardo Hernández Hernández
|
||||
* Ogromny
|
||||
* Oleg Hahm
|
||||
* Stanislav Ochotnický
|
||||
* tiosgz
|
||||
* Yutaro Ohno
|
||||
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Added
|
||||
|
||||
* Support for specifying number of decimals when printing a float tag
|
||||
([#200][200]).
|
||||
* Support for custom font fallbacks ([#153][153]).
|
||||
* overline: new decoration ([#153][153]).
|
||||
* i3/sway: boolean option `strip-workspace-numbers`.
|
||||
* font-shaping: new inheritable configuration option, allowing you to
|
||||
configure whether strings should be _shaped_ using HarfBuzz, or not
|
||||
([#159][159]).
|
||||
* river: support for the new “mode” event present in version 3 of the
|
||||
river status manager protocol, in the form of a new tag, _”mode”_,
|
||||
in the `title` particle.
|
||||
* network: request link stats and expose under tags `dl-speed` and
|
||||
`ul-speed` when `poll-interval` is set.
|
||||
* new module: disk-io.
|
||||
* new module: pulse ([#223][223]).
|
||||
* alsa: `dB` tag ([#202][202]).
|
||||
* mpd: `file` tag ([#219][219]).
|
||||
* pipewire: add a new module for pipewire ([#224][224])
|
||||
* on-click: support `next`/`previous` mouse buttons ([#228][228]).
|
||||
* dwl: add a new module for DWL ([#218][218])
|
||||
* sway: support for workspace ‘rename’ and ‘move’ events
|
||||
([#216][216]).
|
||||
|
||||
[153]: https://codeberg.org/dnkl/yambar/issues/153
|
||||
[159]: https://codeberg.org/dnkl/yambar/issues/159
|
||||
[200]: https://codeberg.org/dnkl/yambar/issues/200
|
||||
[202]: https://codeberg.org/dnkl/yambar/issues/202
|
||||
[218]: https://codeberg.org/dnkl/yambar/pulls/218
|
||||
[219]: https://codeberg.org/dnkl/yambar/pulls/219
|
||||
[223]: https://codeberg.org/dnkl/yambar/pulls/223
|
||||
[224]: https://codeberg.org/dnkl/yambar/pulls/224
|
||||
[228]: https://codeberg.org/dnkl/yambar/pulls/228
|
||||
[216]: https://codeberg.org/dnkl/yambar/issues/216
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* All modules are now compile-time optional.
|
||||
* Minimum required meson version is now 0.59.
|
||||
* Float tags are now treated as floats instead of integers when
|
||||
formatted with the `kb`/`kib`/`mb`/`mib`/`gb`/`gib` string particle
|
||||
formatters.
|
||||
* network: `tx-bitrate` and `rx-bitrate` are now in bits/s instead of
|
||||
Mb/s. Use the `mb` string formatter to render these tags as before
|
||||
(e.g. `string: {text: "{tx-bitrate:mb}"}`).
|
||||
* i3: newly created, and **unfocused** workspaces are now considered
|
||||
non-empty ([#191][191])
|
||||
* alsa: use dB instead of raw volume values, if possible, when
|
||||
calculating the `percent` tag ([#202][202])
|
||||
* cpu: `content` particle is now a template instantiated once for each
|
||||
core, and once for the total CPU usage. See
|
||||
**yambar-modules-cpu**(5) for more information ([#207][207]).
|
||||
* **BREAKING CHANGE**: overhaul of the `map` particle. Instead of
|
||||
specifying a `tag` and then an array of `values`, you must now
|
||||
simply use an array of `conditions`, that consist of:
|
||||
|
||||
`<tag> <operation> <value>`
|
||||
|
||||
where `<operation>` is one of:
|
||||
|
||||
`== != < <= > >=`
|
||||
|
||||
Note that boolean tags must be used as is:
|
||||
|
||||
`online`
|
||||
|
||||
`~online # use '~' to match for their falsehood`
|
||||
|
||||
As an example, if you previously had something like:
|
||||
|
||||
```
|
||||
map:
|
||||
tag: State
|
||||
values:
|
||||
unrecognized:
|
||||
...
|
||||
```
|
||||
|
||||
You would now write it as:
|
||||
|
||||
```
|
||||
map:
|
||||
conditions:
|
||||
State == unrecognized:
|
||||
...
|
||||
```
|
||||
|
||||
Note that if `<value>` contains any non-alphanumerical characters,
|
||||
it **must** be surrounded by `""`:
|
||||
|
||||
`State == "very confused!!!"`
|
||||
|
||||
Finally, you can mix and match conditions using the boolean
|
||||
operators `&&` and `||`:
|
||||
|
||||
```
|
||||
<condition1> && <condition2>
|
||||
<condition1> && (<condition2> || <condition3>) # parenthesis work
|
||||
~(<condition1> && <condition2>) # '~' can be applied to any condition
|
||||
```
|
||||
|
||||
For a more thorough explanation, see the updated map section in the
|
||||
man page for yambar-particles([#137][137], [#175][175] and [#][182]).
|
||||
|
||||
[137]: https://codeberg.org/dnkl/yambar/issues/137
|
||||
[175]: https://codeberg.org/dnkl/yambar/issues/172
|
||||
[182]: https://codeberg.org/dnkl/yambar/issues/182
|
||||
[191]: https://codeberg.org/dnkl/yambar/issues/191
|
||||
[202]: https://codeberg.org/dnkl/yambar/issues/202
|
||||
[207]: https://codeberg.org/dnkl/yambar/issues/207
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* i3: fixed “missing workspace indicator” (_err: modules/i3.c:94:
|
||||
workspace reply/event without 'name' and/or 'output', and/or 'focus'
|
||||
properties_).
|
||||
* Slow/laggy behavior when quickly spawning many `on-click` handlers,
|
||||
e.g. when handling mouse wheel events ([#169][169]).
|
||||
* cpu: don’t error out on systems where SMT has been disabled
|
||||
([#172][172]).
|
||||
* examples/dwl-tags: updated parsing of `output` name ([#178][178]).
|
||||
* sway-xkb: don’t crash when Sway sends an _”added”_ event for a
|
||||
device yambar is already tracking ([#177][177]).
|
||||
* Crash when a particle is “too wide”, and tries to render outside the
|
||||
bar ([#198][198]).
|
||||
* string: crash when failing to convert string to UTF-32.
|
||||
* script: only first transaction processed when receiving multiple
|
||||
transactions in a single batch ([#221][221]).
|
||||
* network: missing SSID (recent kernels, or possibly wireless drivers,
|
||||
no longer provide the SSID in the `NL80211_CMD_NEW_STATION`
|
||||
response) ([#226][226]).
|
||||
* sway-xkb: crash when compositor presents multiple inputs with
|
||||
identical IDs ([#229][229]).
|
||||
|
||||
[169]: https://codeberg.org/dnkl/yambar/issues/169
|
||||
[172]: https://codeberg.org/dnkl/yambar/issues/172
|
||||
[178]: https://codeberg.org/dnkl/yambar/issues/178
|
||||
[177]: https://codeberg.org/dnkl/yambar/issues/177
|
||||
[198]: https://codeberg.org/dnkl/yambar/issues/198
|
||||
[221]: https://codeberg.org/dnkl/yambar/issues/221
|
||||
[226]: https://codeberg.org/dnkl/yambar/issues/226
|
||||
[229]: https://codeberg.org/dnkl/yambar/issues/229
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* Baptiste Daroussin
|
||||
* Horus
|
||||
* Johannes
|
||||
* Leonardo Gibrowski Faé
|
||||
* Leonardo Neumann
|
||||
* Midgard
|
||||
* Ogromny
|
||||
* Peter Rice
|
||||
* Timur Celik
|
||||
* Willem van de Krol
|
||||
* hiog
|
||||
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Added
|
||||
|
||||
* ramp: can now have custom min and max values
|
||||
([#103](https://codeberg.org/dnkl/yambar/issues/103)).
|
||||
* border: new decoration.
|
||||
* i3/sway: new boolean tag: `empty`
|
||||
([#139](https://codeberg.org/dnkl/yambar/issues/139)).
|
||||
* mem: a module handling system memory monitoring
|
||||
* cpu: a module offering cpu usage monitoring
|
||||
* removables: support for audio CDs
|
||||
([#146](https://codeberg.org/dnkl/yambar/issues/146)).
|
||||
* removables: new boolean tag: `audio`.
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* fcft >= 3.0 is now required.
|
||||
* Made `libmpdclient` an optional dependency
|
||||
* battery: unknown battery states are now mapped to ‘unknown’, instead
|
||||
of ‘discharging’.
|
||||
* Wayland: the bar no longer exits when the monitor is
|
||||
disabled/unplugged ([#106](https://codeberg.org/dnkl/yambar/issues/106)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* `left-margin` and `right-margin` from being rejected as invalid
|
||||
options.
|
||||
* Crash when `udev_monitor_receive_device()` returned `NULL`. This
|
||||
affected the “backlight”, “battery” and “removables” modules
|
||||
([#109](https://codeberg.org/dnkl/yambar/issues/109)).
|
||||
* foreign-toplevel: update bar when a top-level is closed.
|
||||
* Bar not being mapped on an output before at least one module has
|
||||
“refreshed” it ([#116](https://codeberg.org/dnkl/yambar/issues/116)).
|
||||
* network: failure to retrieve wireless attributes (SSID, RX/TX
|
||||
bitrate, signal strength etc).
|
||||
* Integer options that were supposed to be >= 0 were incorrectly
|
||||
allowed, leading to various bad things; including yambar crashing,
|
||||
or worse, the compositor crashing
|
||||
([#129](https://codeberg.org/dnkl/yambar/issues/129)).
|
||||
* kib/kb, mib/mb and gib/gb formatters were inverted.
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* [sochotnicky](https://codeberg.org/sochotnicky)
|
||||
* Alexandre Acebedo
|
||||
* anb
|
||||
* Baptiste Daroussin
|
||||
* Catterwocky
|
||||
* horus645
|
||||
* Jan Beich
|
||||
* mz
|
||||
* natemaia
|
||||
* nogerine
|
||||
* Soc Virnyl S. Estela
|
||||
* Vincent Fischer
|
||||
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Added
|
||||
|
||||
* i3: `persistent` attribute, allowing persistent workspaces
|
||||
([#72](https://codeberg.org/dnkl/yambar/issues/72)).
|
||||
* bar: `border.{left,right,top,bottom}-width`, allowing the width of
|
||||
each side of the border to be configured
|
||||
individually. `border.width` is now a short-hand for setting all
|
||||
four borders to the same value
|
||||
([#77](https://codeberg.org/dnkl/yambar/issues/77)).
|
||||
* bar: `layer: top|bottom`, allowing the layer which the bar is
|
||||
rendered on to be changed. Wayland only - ignored on X11.
|
||||
* river: `all-monitors: false|true`.
|
||||
* `-d,--log-level=info|warning|error|none` command line option
|
||||
([#84](https://codeberg.org/dnkl/yambar/issues/84)).
|
||||
* river: support for the river-status protocol, version 2 (‘urgent’
|
||||
views).
|
||||
* `online` tag to the `alsa` module.
|
||||
* alsa: `volume` and `muted` options, allowing you to configure which
|
||||
channels to use as source for the volume level and muted state.
|
||||
* foreign-toplevel: Wayland module that provides information about
|
||||
currently opened windows.
|
||||
* alsa: support for capture devices.
|
||||
* network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags.
|
||||
* network: `poll-interval` option (for the new `signal` and
|
||||
`*-bitrate` tags).
|
||||
* tags: percentage tag formatter, for range tags: `{tag_name:%}`.
|
||||
* tags: kb/mb/gb, and kib/mib/gib tag formatters.
|
||||
* clock: add a config option to show UTC time.
|
||||
|
||||
### Changed
|
||||
|
||||
* bar: do not add `spacing` around empty (zero-width) modules.
|
||||
* alsa: do not error out if we fail to connect to the ALSA device, or
|
||||
if we get disconnected. Instead, keep retrying until we succeed
|
||||
([#86](https://codeberg.org/dnkl/yambar/issues/86)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* `yambar --backend=wayland` always erroring out with _”yambar was
|
||||
compiled without the Wayland backend”_.
|
||||
* Regression: `{where}` tag not being expanded in progress-bar
|
||||
`on-click` handlers.
|
||||
* `alsa` module causing yambar to use 100% CPU if the ALSA device is
|
||||
disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)).
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* [paemuri](https://codeberg.org/paemuri)
|
||||
* [ericonr](https://codeberg.org/ericonr)
|
||||
* [Nulo](https://nulo.in)
|
||||
|
||||
|
||||
## 1.6.2
|
||||
|
||||
### Added
|
||||
|
||||
* Text shaping support.
|
||||
* Support for middle and right mouse buttons, mouse wheel and trackpad
|
||||
scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)).
|
||||
* script: polling mode. See the new `poll-interval` option
|
||||
([#67](https://codeberg.org/dnkl/yambar/issues/67)).
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* doc: split up **yambar-modules**(5) into multiple man pages, one for
|
||||
each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)).
|
||||
* fcft >= 2.4.0 is now required.
|
||||
* sway-xkb: non-keyboard inputs are now ignored
|
||||
([#51](https://codeberg.org/dnkl/yambar/issues/51)).
|
||||
* battery: don’t terminate (causing last status to “freeze”) when
|
||||
failing to update; retry again later
|
||||
([#44](https://codeberg.org/dnkl/yambar/issues/44)).
|
||||
* battery: differentiate "Not Charging" and "Discharging" in state
|
||||
tag of battery module.
|
||||
([#57](https://codeberg.org/dnkl/yambar/issues/57)).
|
||||
* string: use HORIZONTAL ELLIPSIS instead of three regular periods
|
||||
when truncating a string
|
||||
([#73](https://codeberg.org/dnkl/yambar/issues/73)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Crash when merging non-dictionary anchors in the YAML configuration
|
||||
([#32](https://codeberg.org/dnkl/yambar/issues/32)).
|
||||
* Crash in the `ramp` particle when the tag’s value was out-of-bounds
|
||||
([#45](https://codeberg.org/dnkl/yambar/issues/45)).
|
||||
* Crash when a string particle contained `{}`
|
||||
([#48](https://codeberg.org/dnkl/yambar/issues/48)).
|
||||
* `script` module rejecting range tag end values containing the digit
|
||||
`9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)).
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* [novakane](https://codeberg.org/novakane)
|
||||
* [mz](https://codeberg.org/mz)
|
||||
|
||||
|
||||
## 1.6.1
|
||||
|
||||
### Changed
|
||||
|
||||
* i3: workspaces with numerical names are sorted separately from
|
||||
non-numerically named workspaces
|
||||
([#30](https://codeberg.org/dnkl/yambar/issues/30)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* mpd: `elapsed` tag not working (regression, introduced in 1.6.0).
|
||||
* Wrong background color for (semi-) transparent backgrounds.
|
||||
* battery: stats sometimes getting stuck at 0, or impossibly large
|
||||
values ([#25](https://codeberg.org/dnkl/yambar/issues/25)).
|
||||
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Added
|
||||
|
||||
* alsa: `percent` tag. This is an integer tag that represents the
|
||||
current volume as a percentage value
|
||||
([#10](https://codeberg.org/dnkl/yambar/issues/10)).
|
||||
* river: added documentation
|
||||
([#9](https://codeberg.org/dnkl/yambar/issues/9)).
|
||||
* script: new module, adds support for custom user scripts
|
||||
([#11](https://codeberg.org/dnkl/yambar/issues/11)).
|
||||
* mpd: `volume` tag. This is a range tag that represents MPD's current
|
||||
volume in percentage (0-100)
|
||||
* i3: `sort` configuration option, that controls how the workspace
|
||||
list is sorted. Can be set to one of `none`, `ascending` or
|
||||
`descending`. Default is `none`
|
||||
([#17](https://codeberg.org/dnkl/yambar/issues/17)).
|
||||
* i3: `mode` tag: the name of the currently active mode
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* YAML parsing error messages being replaced with a generic _“unknown
|
||||
error”_.
|
||||
* Memory leak when a YAML parsing error was encountered.
|
||||
* clock: update every second when necessary
|
||||
([#12](https://codeberg.org/dnkl/yambar/issues/12)).
|
||||
* mpd: fix compilation with clang
|
||||
([#16](https://codeberg.org/dnkl/yambar/issues/16)).
|
||||
* Crash when the alpha component in a color value was 0.
|
||||
* XCB: Fallback to non-primary monitor when the primary monitor is
|
||||
disconnected ([#20](https://codeberg.org/dnkl/yambar/issues/20))
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* [JorwLNKwpH](https://codeberg.org/JorwLNKwpH)
|
||||
* [optimus-prime](https://codeberg.org/optimus-prime)
|
||||
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Added
|
||||
|
||||
* battery: support for drivers that use `charge_*` (instead of
|
||||
`energy_*`) sys files.
|
||||
* removables: SD card support.
|
||||
* removables: new `ignore` property.
|
||||
* Wayland: multi-seat support.
|
||||
* **Experimental**: 'river': new module for the river Wayland compositor.
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* Requires fcft-2.2.x.
|
||||
* battery: a poll value of 0 disables polling.
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* mpd: check of return value from `thrd_create`.
|
||||
* battery: handle 'manufacturer' and 'model_name' not being present.
|
||||
* Wayland: handle runtime scaling changes.
|
13
PKGBUILD
13
PKGBUILD
|
@ -1,21 +1,24 @@
|
|||
pkgname=yambar
|
||||
pkgver=1.4.0
|
||||
pkgver=1.11.0
|
||||
pkgrel=1
|
||||
pkgdesc="Simplistic and highly configurable status panel for X and Wayland"
|
||||
arch=('x86_64')
|
||||
changelog=CHANGELOG.md
|
||||
arch=('x86_64' 'aarch64')
|
||||
url=https://codeberg.org/dnkl/yambar
|
||||
license=(mit)
|
||||
makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.0')
|
||||
makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.1')
|
||||
depends=(
|
||||
'libxcb' 'xcb-util' 'xcb-util-cursor' 'xcb-util-wm'
|
||||
'wayland' 'wlroots'
|
||||
'wayland'
|
||||
'pixman'
|
||||
'libyaml'
|
||||
'alsa-lib'
|
||||
'libudev.so'
|
||||
'json-c'
|
||||
'libmpdclient'
|
||||
'fcft>=1.1.0')
|
||||
'libpulse'
|
||||
'pipewire'
|
||||
'fcft>=3.0.0' 'fcft<4.0.0')
|
||||
optdepends=('xcb-util-errors: better X error messages')
|
||||
source=()
|
||||
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
pkgname=yambar-wayland
|
||||
pkgver=1.4.0
|
||||
pkgver=1.11.0
|
||||
pkgrel=1
|
||||
pkgdesc="Simplistic and highly configurable status panel for Wayland"
|
||||
arch=('x86_64')
|
||||
arch=('x86_64' 'aarch64')
|
||||
url=https://codeberg.org/dnkl/yambar
|
||||
license=(mit)
|
||||
conflicts=('yambar')
|
||||
provides=('yambar')
|
||||
makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.0')
|
||||
makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.1')
|
||||
depends=(
|
||||
'wayland' 'wlroots'
|
||||
'wayland'
|
||||
'pixman'
|
||||
'libyaml'
|
||||
'alsa-lib'
|
||||
'libudev.so'
|
||||
'json-c'
|
||||
'libmpdclient'
|
||||
'fcft>=1.0.0')
|
||||
'libpulse'
|
||||
'pipewire'
|
||||
'fcft>=3.0.0' 'fcft<4.0.0')
|
||||
source=()
|
||||
changelog=CHANGELOG.md
|
||||
|
||||
pkgver() {
|
||||
cd ../.git &> /dev/null && git describe --tags --long | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' ||
|
||||
|
|
55
README.md
55
README.md
|
@ -1,10 +1,17 @@
|
|||
[](https://ci.codeberg.org/dnkl/yambar)
|
||||
|
||||
# Yambar
|
||||
|
||||
[](https://repology.org/project/yambar/versions)
|
||||
|
||||
|
||||
## Index
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
1. [Configuration](#configuration)
|
||||
1. [Modules](#modules)
|
||||
1. [Installation](#installation)
|
||||
1. [Bugs](#bugs)
|
||||
|
||||
|
||||
## Introduction
|
||||
|
@ -52,14 +59,16 @@ bar:
|
|||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{date}", right-margin: 5}
|
||||
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{time}"}
|
||||
```
|
||||
|
||||
For details, see the man pages (**yambar**(5) is a good start).
|
||||
|
||||
Example configurations can be found in [examples](examples/configurations).
|
||||
|
||||
|
||||
## Modules
|
||||
|
||||
|
@ -69,29 +78,27 @@ Available modules:
|
|||
* backlight
|
||||
* battery
|
||||
* clock
|
||||
* cpu
|
||||
* disk-io
|
||||
* dwl
|
||||
* foreign-toplevel
|
||||
* i3 (and Sway)
|
||||
* label
|
||||
* mem
|
||||
* mpd
|
||||
* network
|
||||
* pipewire
|
||||
* pulse
|
||||
* removables
|
||||
* river
|
||||
* script (see script [examples](examples/scripts))
|
||||
* sway-xkb
|
||||
* xkb (_XCB backend only_)
|
||||
* xwindow (_XCB backend only_)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
If you have not installed [tllist](https://codeberg.org/dnkl/tllist)
|
||||
and [fcft](https://codeberg.org/dnkl/fcft) as system libraries, clone
|
||||
them into the `subprojects` directory:
|
||||
|
||||
```sh
|
||||
mkdir -p subprojects
|
||||
pushd subprojects
|
||||
git clone https://codeberg.org/dnkl/tllist.git
|
||||
git clone https://codeberg.org/dnkl/fcft.git
|
||||
popd
|
||||
```
|
||||
|
||||
To build, first, create a build directory, and switch to it:
|
||||
```sh
|
||||
mkdir -p bld/release && cd bld/release
|
||||
|
@ -100,9 +107,14 @@ mkdir -p bld/release && cd bld/release
|
|||
Second, configure the build (if you intend to install it globally, you
|
||||
might also want `--prefix=/usr`):
|
||||
```sh
|
||||
meson --buildtype=release ../..
|
||||
meson setup --buildtype=release ../..
|
||||
```
|
||||
|
||||
Optionally, explicitly disable a backend (or enable, if you want a
|
||||
configuration error if not all dependencies are met) by adding either
|
||||
`-Dbackend-x11=disabled|enabled` or
|
||||
`-Dbackend-wayland=disabled|enabled` to the meson command line.
|
||||
|
||||
Three, build it:
|
||||
```sh
|
||||
ninja
|
||||
|
@ -112,3 +124,16 @@ Optionally, install it:
|
|||
```sh
|
||||
ninja install
|
||||
```
|
||||
|
||||
## Bugs
|
||||
|
||||
Please report bugs to https://codeberg.org/dnkl/yambar/issues
|
||||
|
||||
The report should contain the following:
|
||||
|
||||
* Which Wayland compositor (and version) you are running
|
||||
* Yambar version (`yambar --version`)
|
||||
* Log output from yambar (start yambar from a terminal)
|
||||
* If reporting a crash, please try to provide a `bt full` backtrace
|
||||
**with symbols** (i.e. use a debug build)
|
||||
* Steps to reproduce. The more details the better
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
struct backend {
|
||||
bool (*setup)(struct bar *bar);
|
||||
void (*cleanup)(struct bar *bar);
|
||||
void (*loop)(struct bar *bar,
|
||||
void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event,
|
||||
int x, int y));
|
||||
void (*loop)(struct bar *bar, void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y));
|
||||
void (*commit)(const struct bar *bar);
|
||||
void (*refresh)(const struct bar *bar);
|
||||
void (*set_cursor)(struct bar *bar, const char *cursor);
|
||||
const char *(*output_name)(const struct bar *bar);
|
||||
};
|
||||
|
|
211
bar/bar.c
211
bar/bar.c
|
@ -1,12 +1,14 @@
|
|||
#include "bar.h"
|
||||
#include "private.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <threads.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
@ -16,15 +18,17 @@
|
|||
#include "../log.h"
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include "xcb.h"
|
||||
#include "xcb.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_WAYLAND)
|
||||
#include "wayland.h"
|
||||
#include "wayland.h"
|
||||
#endif
|
||||
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
/*
|
||||
* Calculate total width of left/center/rigth groups.
|
||||
* Calculate total width of left/center/right groups.
|
||||
* Note: begin_expose() must have been called
|
||||
*/
|
||||
static void
|
||||
|
@ -36,26 +40,33 @@ calculate_widths(const struct private *b, int *left, int *center, int *right)
|
|||
|
||||
for (size_t i = 0; i < b->left.count; i++) {
|
||||
struct exposable *e = b->left.exps[i];
|
||||
assert(e != NULL);
|
||||
*left += b->left_spacing + e->width + b->right_spacing;
|
||||
if (e->width > 0)
|
||||
*left += b->left_spacing + e->width + b->right_spacing;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < b->center.count; i++) {
|
||||
struct exposable *e = b->center.exps[i];
|
||||
assert(e != NULL);
|
||||
*center += b->left_spacing + e->width + b->right_spacing;
|
||||
if (e->width > 0)
|
||||
*center += b->left_spacing + e->width + b->right_spacing;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < b->right.count; i++) {
|
||||
struct exposable *e = b->right.exps[i];
|
||||
assert(e != NULL);
|
||||
*right += b->left_spacing + e->width + b->right_spacing;
|
||||
if (e->width > 0)
|
||||
*right += b->left_spacing + e->width + b->right_spacing;
|
||||
}
|
||||
|
||||
/* No spacing on the edges (that's what the margins are for) */
|
||||
*left -= b->left_spacing + b->right_spacing;
|
||||
*center -= b->left_spacing + b->right_spacing;
|
||||
*right -= b->left_spacing + b->right_spacing;
|
||||
if (*left > 0)
|
||||
*left -= b->left_spacing + b->right_spacing;
|
||||
if (*center > 0)
|
||||
*center -= b->left_spacing + b->right_spacing;
|
||||
if (*right > 0)
|
||||
*right -= b->left_spacing + b->right_spacing;
|
||||
|
||||
assert(*left >= 0);
|
||||
assert(*center >= 0);
|
||||
assert(*right >= 0);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -64,85 +75,94 @@ expose(const struct bar *_bar)
|
|||
const struct private *bar = _bar->private;
|
||||
pixman_image_t *pix = bar->pix;
|
||||
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, pix, &bar->background, 1,
|
||||
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border});
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &bar->background, 1,
|
||||
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border});
|
||||
|
||||
if (bar->border.width > 0) {
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &bar->border.color, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
{0, 0, bar->width, bar->border.width},
|
||||
{0, 0, bar->border.width, bar->height_with_border},
|
||||
{bar->width - bar->border.width, 0, bar->border.width, bar->height_with_border},
|
||||
{0, bar->height_with_border - bar->border.width, bar->width, bar->border.width},
|
||||
});
|
||||
}
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &bar->border.color, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
/* Left */
|
||||
{0, 0, bar->border.left_width, bar->height_with_border},
|
||||
|
||||
/* Right */
|
||||
{bar->width - bar->border.right_width, 0, bar->border.right_width, bar->height_with_border},
|
||||
|
||||
/* Top */
|
||||
{bar->border.left_width, 0, bar->width - bar->border.left_width - bar->border.right_width,
|
||||
bar->border.top_width},
|
||||
|
||||
/* Bottom */
|
||||
{bar->border.left_width, bar->height_with_border - bar->border.bottom_width,
|
||||
bar->width - bar->border.left_width - bar->border.right_width, bar->border.bottom_width},
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
struct module *m = bar->left.mods[i];
|
||||
struct exposable *e = bar->left.exps[i];
|
||||
|
||||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
|
||||
bar->left.exps[i] = module_begin_expose(m);
|
||||
assert(bar->left.exps[i]->width >= 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
struct module *m = bar->center.mods[i];
|
||||
struct exposable *e = bar->center.exps[i];
|
||||
|
||||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
|
||||
bar->center.exps[i] = module_begin_expose(m);
|
||||
assert(bar->center.exps[i]->width >= 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
struct module *m = bar->right.mods[i];
|
||||
struct exposable *e = bar->right.exps[i];
|
||||
|
||||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
|
||||
bar->right.exps[i] = module_begin_expose(m);
|
||||
assert(bar->right.exps[i]->width >= 0);
|
||||
}
|
||||
|
||||
int left_width, center_width, right_width;
|
||||
calculate_widths(bar, &left_width, ¢er_width, &right_width);
|
||||
|
||||
int y = bar->border.width;
|
||||
int x = bar->border.width + bar->left_margin - bar->left_spacing;
|
||||
int y = bar->border.top_width;
|
||||
int x = bar->border.left_width + bar->left_margin - bar->left_spacing;
|
||||
pixman_region32_t clip;
|
||||
pixman_region32_init_rect(
|
||||
&clip, bar->border.left_width + bar->left_margin, bar->border.top_width,
|
||||
(bar->width - bar->left_margin - bar->right_margin - bar->border.left_width - bar->border.right_width),
|
||||
bar->height);
|
||||
pixman_image_set_clip_region32(pix, &clip);
|
||||
pixman_region32_fini(&clip);
|
||||
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
const struct exposable *e = bar->left.exps[i];
|
||||
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
if (e->width > 0)
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
x = bar->width / 2 - center_width / 2 - bar->left_spacing;
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
const struct exposable *e = bar->center.exps[i];
|
||||
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
if (e->width > 0)
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
x = bar->width - (
|
||||
right_width +
|
||||
bar->left_spacing +
|
||||
bar->right_margin +
|
||||
bar->border.width);
|
||||
x = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width);
|
||||
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
const struct exposable *e = bar->right.exps[i];
|
||||
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
if (e->width > 0)
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
bar->backend.iface->commit(_bar);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
refresh(const struct bar *bar)
|
||||
{
|
||||
|
@ -154,25 +174,23 @@ static void
|
|||
set_cursor(struct bar *bar, const char *cursor)
|
||||
{
|
||||
struct private *b = bar->private;
|
||||
|
||||
if (b->cursor_name != NULL && strcmp(b->cursor_name, cursor) == 0)
|
||||
return;
|
||||
|
||||
free(b->cursor_name);
|
||||
b->cursor_name = strdup(cursor);
|
||||
|
||||
b->backend.iface->set_cursor(bar, cursor);
|
||||
}
|
||||
|
||||
static const char *
|
||||
output_name(const struct bar *bar)
|
||||
{
|
||||
const struct private *b = bar->private;
|
||||
return b->backend.iface->output_name(bar);
|
||||
}
|
||||
|
||||
static void
|
||||
on_mouse(struct bar *_bar, enum mouse_event event, int x, int y)
|
||||
on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x, int y)
|
||||
{
|
||||
struct private *bar = _bar->private;
|
||||
|
||||
if ((y < bar->border.width ||
|
||||
y >= (bar->height_with_border - bar->border.width)) ||
|
||||
(x < bar->border.width || x >= (bar->width - bar->border.width)))
|
||||
{
|
||||
if ((y < bar->border.top_width || y >= (bar->height_with_border - bar->border.bottom_width))
|
||||
|| (x < bar->border.left_width || x >= (bar->width - bar->border.right_width))) {
|
||||
set_cursor(_bar, "left_ptr");
|
||||
return;
|
||||
}
|
||||
|
@ -180,14 +198,17 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y)
|
|||
int left_width, center_width, right_width;
|
||||
calculate_widths(bar, &left_width, ¢er_width, &right_width);
|
||||
|
||||
int mx = bar->border.width + bar->left_margin - bar->left_spacing;
|
||||
int mx = bar->border.left_width + bar->left_margin - bar->left_spacing;
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
struct exposable *e = bar->left.exps[i];
|
||||
|
||||
if (e->width == 0)
|
||||
continue;
|
||||
|
||||
mx += bar->left_spacing;
|
||||
if (x >= mx && x < mx + e->width) {
|
||||
if (e->on_mouse != NULL)
|
||||
e->on_mouse(e, _bar, event, x - mx, y);
|
||||
e->on_mouse(e, _bar, event, btn, x - mx, y);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -198,28 +219,31 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y)
|
|||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
struct exposable *e = bar->center.exps[i];
|
||||
|
||||
if (e->width == 0)
|
||||
continue;
|
||||
|
||||
mx += bar->left_spacing;
|
||||
if (x >= mx && x < mx + e->width) {
|
||||
if (e->on_mouse != NULL)
|
||||
e->on_mouse(e, _bar, event, x - mx, y);
|
||||
e->on_mouse(e, _bar, event, btn, x - mx, y);
|
||||
return;
|
||||
}
|
||||
|
||||
mx += e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
mx = bar->width - (right_width
|
||||
+ bar->left_spacing +
|
||||
bar->right_margin +
|
||||
bar->border.width);
|
||||
mx = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width);
|
||||
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
struct exposable *e = bar->right.exps[i];
|
||||
|
||||
if (e->width == 0)
|
||||
continue;
|
||||
|
||||
mx += bar->left_spacing;
|
||||
if (x >= mx && x < mx + e->width) {
|
||||
if (e->on_mouse != NULL)
|
||||
e->on_mouse(e, _bar, event, x - mx, y);
|
||||
e->on_mouse(e, _bar, event, btn, x - mx, y);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -229,13 +253,27 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y)
|
|||
set_cursor(_bar, "left_ptr");
|
||||
}
|
||||
|
||||
static void
|
||||
set_module_thread_name(thrd_t id, struct module *mod)
|
||||
{
|
||||
char title[16];
|
||||
if (mod->description != NULL)
|
||||
strncpy(title, mod->description(mod), sizeof(title));
|
||||
else
|
||||
strncpy(title, "mod:<unknown>", sizeof(title));
|
||||
|
||||
title[15] = '\0';
|
||||
|
||||
if (pthread_setname_np(id, title) < 0)
|
||||
LOG_ERRNO("failed to set thread title");
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct bar *_bar)
|
||||
{
|
||||
struct private *bar = _bar->private;
|
||||
|
||||
bar->height_with_border = bar->height + 2 * bar->border.width;
|
||||
bar->height_with_border = bar->height + bar->border.top_width + bar->border.bottom_width;
|
||||
|
||||
if (!bar->backend.iface->setup(_bar)) {
|
||||
bar->backend.iface->cleanup(_bar);
|
||||
|
@ -245,29 +283,33 @@ run(struct bar *_bar)
|
|||
}
|
||||
|
||||
set_cursor(_bar, "left_ptr");
|
||||
expose(_bar);
|
||||
|
||||
/* Start modules */
|
||||
thrd_t thrd_left[bar->left.count];
|
||||
thrd_t thrd_center[bar->center.count];
|
||||
thrd_t thrd_right[bar->right.count];
|
||||
thrd_t thrd_left[max(bar->left.count, 1)];
|
||||
thrd_t thrd_center[max(bar->center.count, 1)];
|
||||
thrd_t thrd_right[max(bar->right.count, 1)];
|
||||
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
struct module *mod = bar->left.mods[i];
|
||||
|
||||
mod->abort_fd = _bar->abort_fd;
|
||||
thrd_create(&thrd_left[i], (int (*)(void *))bar->left.mods[i]->run, mod);
|
||||
set_module_thread_name(thrd_left[i], mod);
|
||||
}
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
struct module *mod = bar->center.mods[i];
|
||||
|
||||
mod->abort_fd = _bar->abort_fd;
|
||||
thrd_create(&thrd_center[i], (int (*)(void *))bar->center.mods[i]->run, mod);
|
||||
set_module_thread_name(thrd_center[i], mod);
|
||||
}
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
struct module *mod = bar->right.mods[i];
|
||||
|
||||
mod->abort_fd = _bar->abort_fd;
|
||||
thrd_create(&thrd_right[i], (int (*)(void *))bar->right.mods[i]->run, mod);
|
||||
set_module_thread_name(thrd_right[i], mod);
|
||||
}
|
||||
|
||||
LOG_DBG("all modules started");
|
||||
|
@ -281,20 +323,26 @@ run(struct bar *_bar)
|
|||
int mod_ret;
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
thrd_join(thrd_left[i], &mod_ret);
|
||||
if (mod_ret != 0)
|
||||
LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, mod_ret);
|
||||
if (mod_ret != 0) {
|
||||
const struct module *m = bar->left.mods[i];
|
||||
LOG_ERR("module: LEFT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
|
||||
}
|
||||
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
|
||||
}
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
thrd_join(thrd_center[i], &mod_ret);
|
||||
if (mod_ret != 0)
|
||||
LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, mod_ret);
|
||||
if (mod_ret != 0) {
|
||||
const struct module *m = bar->center.mods[i];
|
||||
LOG_ERR("module: CENTER #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
|
||||
}
|
||||
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
|
||||
}
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
thrd_join(thrd_right[i], &mod_ret);
|
||||
if (mod_ret != 0)
|
||||
LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret);
|
||||
if (mod_ret != 0) {
|
||||
const struct module *m = bar->right.mods[i];
|
||||
LOG_ERR("module: RIGHT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
|
||||
}
|
||||
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
|
||||
}
|
||||
|
||||
|
@ -314,7 +362,6 @@ destroy(struct bar *bar)
|
|||
for (size_t i = 0; i < b->left.count; i++) {
|
||||
struct module *m = b->left.mods[i];
|
||||
struct exposable *e = b->left.exps[i];
|
||||
|
||||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
m->destroy(m);
|
||||
|
@ -322,7 +369,6 @@ destroy(struct bar *bar)
|
|||
for (size_t i = 0; i < b->center.count; i++) {
|
||||
struct module *m = b->center.mods[i];
|
||||
struct exposable *e = b->center.exps[i];
|
||||
|
||||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
m->destroy(m);
|
||||
|
@ -330,7 +376,6 @@ destroy(struct bar *bar)
|
|||
for (size_t i = 0; i < b->right.count; i++) {
|
||||
struct module *m = b->right.mods[i];
|
||||
struct exposable *e = b->right.exps[i];
|
||||
|
||||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
m->destroy(m);
|
||||
|
@ -385,7 +430,7 @@ bar_new(const struct bar_config *config)
|
|||
break;
|
||||
|
||||
case BAR_BACKEND_WAYLAND:
|
||||
#if defined(BAR_WAYLAND)
|
||||
#if defined(ENABLE_WAYLAND)
|
||||
backend_data = bar_backend_wayland_new();
|
||||
backend_iface = &wayland_backend_iface;
|
||||
#else
|
||||
|
@ -400,6 +445,7 @@ bar_new(const struct bar_config *config)
|
|||
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->monitor = config->monitor != NULL ? strdup(config->monitor) : NULL;
|
||||
priv->layer = config->layer;
|
||||
priv->location = config->location;
|
||||
priv->height = config->height;
|
||||
priv->background = config->background;
|
||||
|
@ -407,7 +453,11 @@ bar_new(const struct bar_config *config)
|
|||
priv->right_spacing = config->right_spacing;
|
||||
priv->left_margin = config->left_margin;
|
||||
priv->right_margin = config->right_margin;
|
||||
priv->border.width = config->border.width;
|
||||
priv->trackpad_sensitivity = config->trackpad_sensitivity;
|
||||
priv->border.left_width = config->border.left_width;
|
||||
priv->border.right_width = config->border.right_width;
|
||||
priv->border.top_width = config->border.top_width;
|
||||
priv->border.bottom_width = config->border.bottom_width;
|
||||
priv->border.color = config->border.color;
|
||||
priv->border.left_margin = config->border.left_margin;
|
||||
priv->border.right_margin = config->border.right_margin;
|
||||
|
@ -438,6 +488,7 @@ bar_new(const struct bar_config *config)
|
|||
bar->destroy = &destroy;
|
||||
bar->refresh = &refresh;
|
||||
bar->set_cursor = &set_cursor;
|
||||
bar->output_name = &output_name;
|
||||
|
||||
for (size_t i = 0; i < priv->left.count; i++)
|
||||
priv->left.mods[i]->bar = bar;
|
||||
|
|
10
bar/bar.h
10
bar/bar.h
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../color.h"
|
||||
#include "../font-shaping.h"
|
||||
#include "../module.h"
|
||||
|
||||
struct bar {
|
||||
|
@ -12,24 +13,31 @@ struct bar {
|
|||
|
||||
void (*refresh)(const struct bar *bar);
|
||||
void (*set_cursor)(struct bar *bar, const char *cursor);
|
||||
|
||||
const char *(*output_name)(const struct bar *bar);
|
||||
};
|
||||
|
||||
enum bar_location { BAR_TOP, BAR_BOTTOM };
|
||||
enum bar_layer { BAR_LAYER_OVERLAY, BAR_LAYER_TOP, BAR_LAYER_BOTTOM, BAR_LAYER_BACKGROUND };
|
||||
enum bar_backend { BAR_BACKEND_AUTO, BAR_BACKEND_XCB, BAR_BACKEND_WAYLAND };
|
||||
|
||||
struct bar_config {
|
||||
enum bar_backend backend;
|
||||
|
||||
const char *monitor;
|
||||
enum bar_layer layer;
|
||||
enum bar_location location;
|
||||
enum font_shaping font_shaping;
|
||||
int height;
|
||||
int left_spacing, right_spacing;
|
||||
int left_margin, right_margin;
|
||||
int trackpad_sensitivity;
|
||||
|
||||
pixman_color_t background;
|
||||
|
||||
struct {
|
||||
int width;
|
||||
int left_width, right_width;
|
||||
int top_width, bottom_width;
|
||||
pixman_color_t color;
|
||||
int left_margin, right_margin;
|
||||
int top_margin, bottom_margin;
|
||||
|
|
|
@ -7,15 +7,14 @@ endif
|
|||
|
||||
if backend_wayland
|
||||
wayland_protocols = dependency('wayland-protocols')
|
||||
wayland_protocols_datadir = wayland_protocols.get_pkgconfig_variable('pkgdatadir')
|
||||
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
|
||||
|
||||
wscanner = dependency('wayland-scanner', native: true)
|
||||
wscanner_prog = find_program(
|
||||
wscanner.get_pkgconfig_variable('wayland_scanner'), native: true)
|
||||
wscanner.get_variable('wayland_scanner'), native: true)
|
||||
|
||||
wl_proto_headers = []
|
||||
wl_proto_src = []
|
||||
generated_wayland_protocols = []
|
||||
foreach prot : [
|
||||
'../external/wlr-layer-shell-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
|
||||
|
|
|
@ -3,18 +3,22 @@
|
|||
#include "../bar/bar.h"
|
||||
#include "backend.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
/* From bar_config */
|
||||
char *monitor;
|
||||
enum bar_layer layer;
|
||||
enum bar_location location;
|
||||
int height;
|
||||
int left_spacing, right_spacing;
|
||||
int left_margin, right_margin;
|
||||
int trackpad_sensitivity;
|
||||
|
||||
pixman_color_t background;
|
||||
|
||||
struct {
|
||||
int width;
|
||||
int left_width, right_width;
|
||||
int top_width, bottom_width;
|
||||
pixman_color_t color;
|
||||
int left_margin, right_margin;
|
||||
int top_margin, bottom_margin;
|
||||
|
@ -40,9 +44,6 @@ struct private {
|
|||
int width;
|
||||
int height_with_border;
|
||||
|
||||
/* Name of currently active cursor */
|
||||
char *cursor_name;
|
||||
|
||||
pixman_image_t *pix;
|
||||
|
||||
struct {
|
||||
|
|
1157
bar/wayland.c
1157
bar/wayland.c
File diff suppressed because it is too large
Load diff
254
bar/xcb.c
254
bar/xcb.c
|
@ -1,15 +1,16 @@
|
|||
#include "xcb.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pixman.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/randr.h>
|
||||
#include <xcb/render.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xcb_cursor.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
|
@ -32,12 +33,12 @@ struct xcb_backend {
|
|||
xcb_gc_t gc;
|
||||
xcb_cursor_context_t *cursor_ctx;
|
||||
xcb_cursor_t cursor;
|
||||
const char *xcursor;
|
||||
|
||||
uint8_t depth;
|
||||
void *client_pixmap;
|
||||
size_t client_pixmap_size;
|
||||
pixman_image_t *pix;
|
||||
|
||||
};
|
||||
|
||||
void *
|
||||
|
@ -53,11 +54,8 @@ setup(struct bar *_bar)
|
|||
struct private *bar = _bar->private;
|
||||
struct xcb_backend *backend = bar->backend.data;
|
||||
|
||||
if (bar->border.left_margin != 0 ||
|
||||
bar->border.right_margin != 0 ||
|
||||
bar->border.top_margin != 0 ||
|
||||
bar->border.bottom_margin)
|
||||
{
|
||||
if (bar->border.left_margin != 0 || bar->border.right_margin != 0 || bar->border.top_margin != 0
|
||||
|| bar->border.bottom_margin) {
|
||||
LOG_WARN("non-zero border margins ignored in X11 backend");
|
||||
}
|
||||
|
||||
|
@ -74,10 +72,8 @@ setup(struct bar *_bar)
|
|||
|
||||
xcb_screen_t *screen = xcb_aux_get_screen(backend->conn, default_screen);
|
||||
|
||||
xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply(
|
||||
backend->conn,
|
||||
xcb_randr_get_monitors(backend->conn, screen->root, 0),
|
||||
&e);
|
||||
xcb_randr_get_monitors_reply_t *monitors
|
||||
= xcb_randr_get_monitors_reply(backend->conn, xcb_randr_get_monitors(backend->conn, screen->root, 0), &e);
|
||||
|
||||
if (e != NULL) {
|
||||
LOG_ERR("failed to get monitor list: %s", xcb_error(e));
|
||||
|
@ -88,39 +84,43 @@ setup(struct bar *_bar)
|
|||
|
||||
/* Find monitor coordinates and width/height */
|
||||
bool found_monitor = false;
|
||||
for (xcb_randr_monitor_info_iterator_t it =
|
||||
xcb_randr_get_monitors_monitors_iterator(monitors);
|
||||
it.rem > 0;
|
||||
xcb_randr_monitor_info_next(&it))
|
||||
{
|
||||
for (xcb_randr_monitor_info_iterator_t it = xcb_randr_get_monitors_monitors_iterator(monitors); it.rem > 0;
|
||||
xcb_randr_monitor_info_next(&it)) {
|
||||
const xcb_randr_monitor_info_t *mon = it.data;
|
||||
char *name = get_atom_name(backend->conn, mon->name);
|
||||
|
||||
LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name,
|
||||
mon->width, mon->height, mon->x, mon->y,
|
||||
mon->width_in_millimeters, mon->height_in_millimeters);
|
||||
LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name, mon->width, mon->height, mon->x, mon->y,
|
||||
mon->width_in_millimeters, mon->height_in_millimeters);
|
||||
|
||||
if (!((bar->monitor == NULL && mon->primary) ||
|
||||
(bar->monitor != NULL && strcmp(bar->monitor, name) == 0)))
|
||||
{
|
||||
/* User wants a specific monitor, and this is not the one */
|
||||
if (bar->monitor != NULL && strcmp(bar->monitor, name) != 0) {
|
||||
free(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
free(name);
|
||||
|
||||
backend->x = mon->x;
|
||||
backend->y = mon->y;
|
||||
bar->width = mon->width;
|
||||
backend->y += bar->location == BAR_TOP ? 0
|
||||
: screen->height_in_pixels - bar->height_with_border;
|
||||
backend->y += bar->location == BAR_TOP ? 0 : mon->height - bar->height_with_border;
|
||||
|
||||
found_monitor = true;
|
||||
break;
|
||||
|
||||
if ((bar->monitor != NULL && strcmp(bar->monitor, name) == 0) || (bar->monitor == NULL && mon->primary)) {
|
||||
/* Exact match */
|
||||
free(name);
|
||||
break;
|
||||
}
|
||||
|
||||
free(name);
|
||||
}
|
||||
free(monitors);
|
||||
|
||||
if (!found_monitor) {
|
||||
LOG_ERR("no matching monitor");
|
||||
if (bar->monitor == NULL)
|
||||
LOG_ERR("no monitors found");
|
||||
else
|
||||
LOG_ERR("no monitor '%s'", bar->monitor);
|
||||
|
||||
/* TODO: cleanup */
|
||||
return false;
|
||||
}
|
||||
|
@ -142,74 +142,47 @@ setup(struct bar *_bar)
|
|||
LOG_DBG("using a %hhu-bit visual", depth);
|
||||
|
||||
backend->colormap = xcb_generate_id(backend->conn);
|
||||
xcb_create_colormap(
|
||||
backend->conn, 0, backend->colormap, screen->root, vis->visual_id);
|
||||
xcb_create_colormap(backend->conn, 0, backend->colormap, screen->root, vis->visual_id);
|
||||
|
||||
backend->win = xcb_generate_id(backend->conn);
|
||||
xcb_create_window(
|
||||
backend->conn,
|
||||
depth, backend->win, screen->root,
|
||||
backend->x, backend->y, bar->width, bar->height_with_border,
|
||||
0,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
|
||||
(XCB_CW_BACK_PIXEL |
|
||||
XCB_CW_BORDER_PIXEL |
|
||||
XCB_CW_EVENT_MASK |
|
||||
XCB_CW_COLORMAP),
|
||||
(const uint32_t []){
|
||||
screen->black_pixel,
|
||||
screen->white_pixel,
|
||||
(XCB_EVENT_MASK_EXPOSURE |
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE |
|
||||
XCB_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_EVENT_MASK_POINTER_MOTION |
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY),
|
||||
backend->colormap}
|
||||
);
|
||||
backend->conn, depth, backend->win, screen->root, backend->x, backend->y, bar->width, bar->height_with_border,
|
||||
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
|
||||
(XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP),
|
||||
(const uint32_t[]){screen->black_pixel, screen->white_pixel,
|
||||
(XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS
|
||||
| XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_STRUCTURE_NOTIFY),
|
||||
backend->colormap});
|
||||
|
||||
const char *title = "yambar";
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
||||
strlen(title), title);
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
||||
strlen(title), title);
|
||||
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){getpid()});
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32,
|
||||
1, (const uint32_t []){_NET_WM_WINDOW_TYPE_DOCK});
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32,
|
||||
2, (const uint32_t []){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){0xffffffff});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1,
|
||||
(const uint32_t[]){getpid()});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 1,
|
||||
(const uint32_t[]){_NET_WM_WINDOW_TYPE_DOCK});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STATE, XCB_ATOM_ATOM, 32, 2,
|
||||
(const uint32_t[]){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1,
|
||||
(const uint32_t[]){0xffffffff});
|
||||
|
||||
/* Always on top */
|
||||
xcb_configure_window(
|
||||
backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE,
|
||||
(const uint32_t []){XCB_STACK_MODE_ABOVE});
|
||||
xcb_configure_window(backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE,
|
||||
(const uint32_t[]){XCB_STACK_MODE_ABOVE});
|
||||
|
||||
uint32_t top_strut, bottom_strut;
|
||||
uint32_t top_pair[2], bottom_pair[2];
|
||||
|
||||
if (bar->location == BAR_TOP) {
|
||||
top_strut = backend->y + bar->height_with_border;
|
||||
top_strut = bar->height_with_border;
|
||||
top_pair[0] = backend->x;
|
||||
top_pair[1] = backend->x + bar->width - 1;
|
||||
|
||||
bottom_strut = 0;
|
||||
bottom_pair[0] = bottom_pair[1] = 0;
|
||||
} else {
|
||||
bottom_strut = screen->height_in_pixels - backend->y;
|
||||
bottom_strut = bar->height_with_border;
|
||||
bottom_pair[0] = backend->x;
|
||||
bottom_pair[1] = backend->x + bar->width - 1;
|
||||
|
||||
|
@ -219,42 +192,38 @@ setup(struct bar *_bar)
|
|||
|
||||
uint32_t strut[] = {
|
||||
/* left/right/top/bottom */
|
||||
0, 0,
|
||||
0,
|
||||
0,
|
||||
top_strut,
|
||||
bottom_strut,
|
||||
|
||||
/* start/end pairs for left/right/top/bottom */
|
||||
0, 0,
|
||||
0, 0,
|
||||
top_pair[0], top_pair[1],
|
||||
bottom_pair[0], bottom_pair[1],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
top_pair[0],
|
||||
top_pair[1],
|
||||
bottom_pair[0],
|
||||
bottom_pair[1],
|
||||
};
|
||||
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_STRUT, XCB_ATOM_CARDINAL, 32,
|
||||
4, strut);
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4,
|
||||
strut);
|
||||
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32,
|
||||
12, strut);
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL,
|
||||
32, 12, strut);
|
||||
|
||||
backend->gc = xcb_generate_id(backend->conn);
|
||||
xcb_create_gc(backend->conn, backend->gc, backend->win,
|
||||
XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
|
||||
(const uint32_t []){screen->white_pixel, 0});
|
||||
xcb_create_gc(backend->conn, backend->gc, backend->win, XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
|
||||
(const uint32_t[]){screen->white_pixel, 0});
|
||||
|
||||
const uint32_t stride = stride_for_format_and_width(
|
||||
PIXMAN_a8r8g8b8, bar->width);
|
||||
const uint32_t stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, bar->width);
|
||||
|
||||
backend->client_pixmap_size = stride * bar->height_with_border;
|
||||
backend->client_pixmap = malloc(backend->client_pixmap_size);
|
||||
backend->pix = pixman_image_create_bits_no_clear(
|
||||
PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
|
||||
(uint32_t *)backend->client_pixmap, stride);
|
||||
backend->pix = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
|
||||
(uint32_t *)backend->client_pixmap, stride);
|
||||
bar->pix = backend->pix;
|
||||
|
||||
xcb_map_window(backend->conn, backend->win);
|
||||
|
@ -280,9 +249,6 @@ cleanup(struct bar *_bar)
|
|||
if (backend->cursor_ctx != NULL)
|
||||
xcb_cursor_context_free(backend->cursor_ctx);
|
||||
|
||||
/* TODO: move to bar.c */
|
||||
free(bar->cursor_name);
|
||||
|
||||
if (backend->pix != NULL)
|
||||
pixman_image_unref(backend->pix);
|
||||
free(backend->client_pixmap);
|
||||
|
@ -300,20 +266,18 @@ cleanup(struct bar *_bar)
|
|||
}
|
||||
|
||||
static void
|
||||
loop(struct bar *_bar,
|
||||
void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event, int x, int y))
|
||||
loop(struct bar *_bar, void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y))
|
||||
{
|
||||
struct private *bar = _bar->private;
|
||||
struct xcb_backend *backend = bar->backend.data;
|
||||
|
||||
pthread_setname_np(pthread_self(), "bar(xcb)");
|
||||
|
||||
const int fd = xcb_get_file_descriptor(backend->conn);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = _bar->abort_fd, .events = POLLIN},
|
||||
{.fd = fd, .events = POLLIN}
|
||||
};
|
||||
struct pollfd fds[] = {{.fd = _bar->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}};
|
||||
|
||||
poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
|
||||
|
@ -322,18 +286,14 @@ loop(struct bar *_bar,
|
|||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_WARN("disconnected from XCB");
|
||||
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
LOG_ERRNO("failed to signal abort to modules");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn);
|
||||
e != NULL;
|
||||
e = xcb_poll_for_event(backend->conn))
|
||||
{
|
||||
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn); e != NULL;
|
||||
e = xcb_poll_for_event(backend->conn)) {
|
||||
switch (XCB_EVENT_RESPONSE_TYPE(e)) {
|
||||
case 0:
|
||||
LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e));
|
||||
|
@ -345,7 +305,7 @@ loop(struct bar *_bar,
|
|||
|
||||
case XCB_MOTION_NOTIFY: {
|
||||
const xcb_motion_notify_event_t *evt = (void *)e;
|
||||
on_mouse(_bar, ON_MOUSE_MOTION, evt->event_x, evt->event_y);
|
||||
on_mouse(_bar, ON_MOUSE_MOTION, MOUSE_BTN_NONE, evt->event_x, evt->event_y);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -354,7 +314,16 @@ loop(struct bar *_bar,
|
|||
|
||||
case XCB_BUTTON_RELEASE: {
|
||||
const xcb_button_release_event_t *evt = (void *)e;
|
||||
on_mouse(_bar, ON_MOUSE_CLICK, evt->event_x, evt->event_y);
|
||||
|
||||
switch (evt->detail) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
on_mouse(_bar, ON_MOUSE_CLICK, evt->detail, evt->event_x, evt->event_y);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -386,10 +355,9 @@ commit(const struct bar *_bar)
|
|||
const struct private *bar = _bar->private;
|
||||
const struct xcb_backend *backend = bar->backend.data;
|
||||
|
||||
xcb_put_image(
|
||||
backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc,
|
||||
bar->width, bar->height_with_border, 0, 0, 0,
|
||||
backend->depth, backend->client_pixmap_size, backend->client_pixmap);
|
||||
xcb_put_image(backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc, bar->width,
|
||||
bar->height_with_border, 0, 0, 0, backend->depth, backend->client_pixmap_size,
|
||||
backend->client_pixmap);
|
||||
xcb_flush(backend->conn);
|
||||
}
|
||||
|
||||
|
@ -401,23 +369,19 @@ refresh(const struct bar *_bar)
|
|||
|
||||
/* Send an event to handle refresh from main thread */
|
||||
|
||||
/* Note: docs say that all X11 events are 32 bytes, reglardless of
|
||||
/* Note: docs say that all X11 events are 32 bytes, regardless of
|
||||
* the size of the event structure */
|
||||
xcb_expose_event_t *evt = calloc(32, 1);
|
||||
|
||||
*evt = (xcb_expose_event_t){
|
||||
.response_type = XCB_EXPOSE,
|
||||
.window = backend->win,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = bar->width,
|
||||
.height = bar->height,
|
||||
.count = 1
|
||||
};
|
||||
*evt = (xcb_expose_event_t){.response_type = XCB_EXPOSE,
|
||||
.window = backend->win,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = bar->width,
|
||||
.height = bar->height,
|
||||
.count = 1};
|
||||
|
||||
xcb_send_event(
|
||||
backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE,
|
||||
(char *)evt);
|
||||
xcb_send_event(backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE, (char *)evt);
|
||||
|
||||
xcb_flush(backend->conn);
|
||||
free(evt);
|
||||
|
@ -432,12 +396,21 @@ set_cursor(struct bar *_bar, const char *cursor)
|
|||
if (backend->cursor_ctx == NULL)
|
||||
return;
|
||||
|
||||
if (backend->xcursor != NULL && strcmp(backend->xcursor, cursor) == 0)
|
||||
return;
|
||||
|
||||
if (backend->cursor != 0)
|
||||
xcb_free_cursor(backend->conn, backend->cursor);
|
||||
|
||||
backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor);
|
||||
xcb_change_window_attributes(
|
||||
backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
|
||||
xcb_change_window_attributes(backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
|
||||
}
|
||||
|
||||
static const char *
|
||||
output_name(const struct bar *_bar)
|
||||
{
|
||||
/* Not implemented */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct backend xcb_backend_iface = {
|
||||
|
@ -447,4 +420,5 @@ const struct backend xcb_backend_iface = {
|
|||
.commit = &commit,
|
||||
.refresh = &refresh,
|
||||
.set_cursor = &set_cursor,
|
||||
.output_name = &output_name,
|
||||
};
|
||||
|
|
83
char32.c
Normal file
83
char32.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "char32.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#if defined __has_include
|
||||
#if __has_include(<stdc-predef.h>)
|
||||
#include <stdc-predef.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define LOG_MODULE "char32"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* For now, assume we can map directly to the corresponding wchar_t
|
||||
* functions. This is true if:
|
||||
*
|
||||
* - both data types have the same size
|
||||
* - both use the same encoding (though we require that encoding to be UTF-32)
|
||||
*/
|
||||
|
||||
_Static_assert(sizeof(wchar_t) == sizeof(char32_t), "wchar_t vs. char32_t size mismatch");
|
||||
|
||||
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||
#error "char32_t does not use UTF-32"
|
||||
#endif
|
||||
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__)
|
||||
#error "wchar_t does not use UTF-32"
|
||||
#endif
|
||||
|
||||
size_t
|
||||
c32len(const char32_t *s)
|
||||
{
|
||||
return wcslen((const wchar_t *)s);
|
||||
}
|
||||
|
||||
char32_t *
|
||||
ambstoc32(const char *src)
|
||||
{
|
||||
if (src == NULL)
|
||||
return NULL;
|
||||
|
||||
const size_t src_len = strlen(src);
|
||||
|
||||
char32_t *ret = malloc((src_len + 1) * sizeof(ret[0]));
|
||||
if (ret == NULL)
|
||||
return NULL;
|
||||
|
||||
mbstate_t ps = {0};
|
||||
char32_t *out = ret;
|
||||
const char *in = src;
|
||||
const char *const end = src + src_len + 1;
|
||||
|
||||
size_t chars = 0;
|
||||
size_t rc;
|
||||
|
||||
while ((rc = mbrtoc32(out, in, end - in, &ps)) != 0) {
|
||||
switch (rc) {
|
||||
case (size_t)-1:
|
||||
case (size_t)-2:
|
||||
case (size_t)-3:
|
||||
goto err;
|
||||
}
|
||||
|
||||
in += rc;
|
||||
out++;
|
||||
chars++;
|
||||
}
|
||||
|
||||
*out = U'\0';
|
||||
|
||||
ret = realloc(ret, (chars + 1) * sizeof(ret[0]));
|
||||
return ret;
|
||||
|
||||
err:
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
7
char32.h
Normal file
7
char32.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <uchar.h>
|
||||
|
||||
size_t c32len(const char32_t *s);
|
||||
char32_t *ambstoc32(const char *src);
|
|
@ -8,5 +8,6 @@ _arguments \
|
|||
'(-c --config)'{-c,--config}'[alternative configuration file]:filename:_files' \
|
||||
'(-C --validate)'{-C,--validate}'[verify configuration then quit]' \
|
||||
'(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running]:pidfile:_files' \
|
||||
'(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \
|
||||
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \
|
||||
'(-s --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging]'
|
||||
|
|
184
config-verify.c
184
config-verify.c
|
@ -1,7 +1,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <tllist.h>
|
||||
|
||||
|
@ -16,11 +16,9 @@ conf_err_prefix(const keychain_t *chain, const struct yml_node *node)
|
|||
static char msg[4096];
|
||||
int idx = 0;
|
||||
|
||||
idx += snprintf(&msg[idx], sizeof(msg) - idx, "%zu:%zu: ",
|
||||
yml_source_line(node), yml_source_column(node));
|
||||
idx += snprintf(&msg[idx], sizeof(msg) - idx, "%zu:%zu: ", yml_source_line(node), yml_source_column(node));
|
||||
|
||||
tll_foreach(*chain, key)
|
||||
idx += snprintf(&msg[idx], sizeof(msg) - idx, "%s.", key->item);
|
||||
tll_foreach(*chain, key) idx += snprintf(&msg[idx], sizeof(msg) - idx, "%s.", key->item);
|
||||
|
||||
/* Remove trailing "." */
|
||||
msg[idx - 1] = '\0';
|
||||
|
@ -45,14 +43,49 @@ conf_verify_int(keychain_t *chain, const struct yml_node *node)
|
|||
if (yml_value_is_int(node))
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not an integer: '%s'",
|
||||
conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
LOG_ERR("%s: value is not an integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_enum(keychain_t *chain, const struct yml_node *node,
|
||||
const char *values[], size_t count)
|
||||
conf_verify_unsigned(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (yml_value_is_int(node) && yml_value_as_int(node) >= 0)
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not a positive integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_bool(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (yml_value_is_bool(node))
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not a boolean: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_list(keychain_t *chain, const struct yml_node *node,
|
||||
bool (*verify)(keychain_t *chain, const struct yml_node *node))
|
||||
{
|
||||
if (!yml_is_list(node)) {
|
||||
LOG_ERR("%s: value is not a list", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_list_iter iter = yml_list_iter(node); iter.node != NULL; yml_list_next(&iter)) {
|
||||
if (!verify(chain, iter.node))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count)
|
||||
{
|
||||
const char *s = yml_value_as_string(node);
|
||||
if (s == NULL) {
|
||||
|
@ -73,8 +106,7 @@ conf_verify_enum(keychain_t *chain, const struct yml_node *node,
|
|||
}
|
||||
|
||||
bool
|
||||
conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
||||
const struct attr_info info[])
|
||||
conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct attr_info info[])
|
||||
{
|
||||
if (!yml_is_dict(node)) {
|
||||
LOG_ERR("%s: must be a dictionary", conf_err_prefix(chain, node));
|
||||
|
@ -89,10 +121,7 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
|||
bool exists[count];
|
||||
memset(exists, 0, sizeof(exists));
|
||||
|
||||
for (struct yml_dict_iter it = yml_dict_iter(node);
|
||||
it.key != NULL;
|
||||
yml_dict_next(&it))
|
||||
{
|
||||
for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) {
|
||||
const char *key = yml_value_as_string(it.key);
|
||||
if (key == NULL) {
|
||||
LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key));
|
||||
|
@ -132,6 +161,48 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_on_click_path(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_string(chain, node))
|
||||
return false;
|
||||
|
||||
#if 1
|
||||
/* We allow non-absolute paths in on-click handlers */
|
||||
return true;
|
||||
#else
|
||||
const char *path = yml_value_as_string(node);
|
||||
|
||||
const bool is_absolute = path[0] == '/';
|
||||
const bool is_tilde = path[0] == '~' && path[1] == '/';
|
||||
|
||||
if (!is_absolute && !is_tilde) {
|
||||
LOG_ERR("%s: path must be either absolute, or begin with '~/", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_on_click(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
/* on-click: <command> */
|
||||
const char *s = yml_value_as_string(node);
|
||||
if (s != NULL)
|
||||
return verify_on_click_path(chain, node);
|
||||
|
||||
static const struct attr_info info[] = {
|
||||
{"left", false, &verify_on_click_path}, {"middle", false, &verify_on_click_path},
|
||||
{"right", false, &verify_on_click_path}, {"wheel-up", false, &verify_on_click_path},
|
||||
{"wheel-down", false, &verify_on_click_path}, {"previous", false, &verify_on_click_path},
|
||||
{"next", false, &verify_on_click_path}, {NULL, false, NULL},
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, info);
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_color(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
|
@ -145,27 +216,30 @@ conf_verify_color(keychain_t *chain, const struct yml_node *node)
|
|||
int v = sscanf(s, "%02x%02x%02x%02x", &r, &g, &b, &a);
|
||||
|
||||
if (strlen(s) != 8 || v != 4) {
|
||||
LOG_ERR("%s: value must be a color ('rrggbbaa', e.g ff00ffff)",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: value must be a color ('rrggbbaa', e.g ff00ffff)", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
conf_verify_font(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_scalar(node)) {
|
||||
LOG_ERR("%s: font must be a fontconfig-formatted string",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: font must be a fontconfig-formatted string", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_enum(chain, node, (const char *[]){"full", /*"graphemes",*/ "none"}, 2);
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
|
@ -173,7 +247,8 @@ conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
if (yml_dict_length(node) != 1) {
|
||||
LOG_ERR("%s: decoration must be a dictionary with a single key; "
|
||||
"the name of the particle", conf_err_prefix(chain, node));
|
||||
"the name of the particle",
|
||||
conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -189,8 +264,7 @@ conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
const struct deco_iface *iface = plugin_load_deco(deco_name);
|
||||
if (iface == NULL) {
|
||||
LOG_ERR("%s: invalid decoration name: %s",
|
||||
conf_err_prefix(chain, deco), deco_name);
|
||||
LOG_ERR("%s: invalid decoration name: %s", conf_err_prefix(chain, deco), deco_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -207,10 +281,7 @@ conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
assert(yml_is_list(node));
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it))
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
|
||||
if (!conf_verify_particle(chain, it.node))
|
||||
return false;
|
||||
}
|
||||
|
@ -225,7 +296,8 @@ conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
if (yml_dict_length(node) != 1) {
|
||||
LOG_ERR("%s: particle must be a dictionary with a single key; "
|
||||
"the name of the particle", conf_err_prefix(chain, node));
|
||||
"the name of the particle",
|
||||
conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -241,8 +313,7 @@ conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
const struct particle_iface *iface = plugin_load_particle(particle_name);
|
||||
if (iface == NULL) {
|
||||
LOG_ERR("%s: invalid particle name: %s",
|
||||
conf_err_prefix(chain, particle), particle_name);
|
||||
LOG_ERR("%s: invalid particle name: %s", conf_err_prefix(chain, particle), particle_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -264,19 +335,18 @@ conf_verify_particle(keychain_t *chain, const struct yml_node *node)
|
|||
else if (yml_is_list(node))
|
||||
return conf_verify_particle_list_items(chain, node);
|
||||
else {
|
||||
LOG_ERR("%s: particle must be either a dictionary or a list",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: particle must be either a dictionary or a list", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
verify_module(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_dict(node) || yml_dict_length(node) != 1) {
|
||||
LOG_ERR("%s: module must be a dictionary with a single key; "
|
||||
"the name of the module", conf_err_prefix(chain, node));
|
||||
"the name of the module",
|
||||
conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -292,8 +362,7 @@ verify_module(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
const struct module_iface *iface = plugin_load_module(mod_name);
|
||||
if (iface == NULL) {
|
||||
LOG_ERR(
|
||||
"%s: invalid module name: %s", conf_err_prefix(chain, node), mod_name);
|
||||
LOG_ERR("%s: invalid module name: %s", conf_err_prefix(chain, node), mod_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -315,10 +384,7 @@ verify_module_list(keychain_t *chain, const struct yml_node *node)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it))
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
|
||||
if (!verify_module(chain, it.node))
|
||||
return false;
|
||||
}
|
||||
|
@ -330,14 +396,12 @@ static bool
|
|||
verify_bar_border(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"width", false, &conf_verify_int},
|
||||
{"color", false, &conf_verify_color},
|
||||
{"margin", false, &conf_verify_int},
|
||||
{"left-margin", false, &conf_verify_int},
|
||||
{"right-margin", false, &conf_verify_int},
|
||||
{"top-margin", false, &conf_verify_int},
|
||||
{"bottom-margin", false, &conf_verify_int},
|
||||
{NULL, false, NULL},
|
||||
{"width", false, &conf_verify_unsigned}, {"left-width", false, &conf_verify_unsigned},
|
||||
{"right-width", false, &conf_verify_unsigned}, {"top-width", false, &conf_verify_unsigned},
|
||||
{"bottom-width", false, &conf_verify_unsigned}, {"color", false, &conf_verify_color},
|
||||
{"margin", false, &conf_verify_unsigned}, {"left-margin", false, &conf_verify_unsigned},
|
||||
{"right-margin", false, &conf_verify_unsigned}, {"top-margin", false, &conf_verify_unsigned},
|
||||
{"bottom-margin", false, &conf_verify_unsigned}, {NULL, false, NULL},
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
|
@ -349,6 +413,12 @@ verify_bar_location(keychain_t *chain, const struct yml_node *node)
|
|||
return conf_verify_enum(chain, node, (const char *[]){"top", "bottom"}, 2);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_bar_layer(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_enum(chain, node, (const char *[]){"overlay", "top", "bottom", "background"}, 4);
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_bar(const struct yml_node *bar)
|
||||
{
|
||||
|
@ -361,28 +431,32 @@ conf_verify_bar(const struct yml_node *bar)
|
|||
chain_push(&chain, "bar");
|
||||
|
||||
static const struct attr_info attrs[] = {
|
||||
{"height", true, &conf_verify_int},
|
||||
{"height", true, &conf_verify_unsigned},
|
||||
{"location", true, &verify_bar_location},
|
||||
{"background", true, &conf_verify_color},
|
||||
|
||||
{"monitor", false, &conf_verify_string},
|
||||
{"layer", false, &verify_bar_layer},
|
||||
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
|
||||
{"margin", false, &conf_verify_int},
|
||||
{"left_margin", false, &conf_verify_int},
|
||||
{"right_margin", false, &conf_verify_int},
|
||||
{"margin", false, &conf_verify_unsigned},
|
||||
{"left-margin", false, &conf_verify_unsigned},
|
||||
{"right-margin", false, &conf_verify_unsigned},
|
||||
|
||||
{"border", false, &verify_bar_border},
|
||||
{"font", false, &conf_verify_font},
|
||||
{"font-shaping", false, &conf_verify_font_shaping},
|
||||
{"foreground", false, &conf_verify_color},
|
||||
|
||||
{"left", false, &verify_module_list},
|
||||
{"center", false, &verify_module_list},
|
||||
{"right", false, &verify_module_list},
|
||||
|
||||
{"trackpad-sensitivity", false, &conf_verify_unsigned},
|
||||
|
||||
{NULL, false, NULL},
|
||||
};
|
||||
|
||||
|
|
|
@ -26,20 +26,23 @@ chain_pop(keychain_t *chain)
|
|||
tll_pop_back(*chain);
|
||||
}
|
||||
|
||||
const char *conf_err_prefix(
|
||||
const keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
const char *conf_err_prefix(const keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_string(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_int(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_unsigned(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_bool(keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_enum(keychain_t *chain, const struct yml_node *node,
|
||||
const char *values[], size_t count);
|
||||
bool conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count);
|
||||
bool conf_verify_list(keychain_t *chain, const struct yml_node *node,
|
||||
bool (*verify)(keychain_t *chain, const struct yml_node *node));
|
||||
bool conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
||||
const struct attr_info info[]); /* NULL-terminated list */
|
||||
|
||||
bool conf_verify_on_click(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_color(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_font(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_particle(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node);
|
||||
|
|
259
config.c
259
config.c
|
@ -1,9 +1,10 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
|
@ -20,9 +21,7 @@
|
|||
static uint8_t
|
||||
hex_nibble(char hex)
|
||||
{
|
||||
assert((hex >= '0' && hex <= '9') ||
|
||||
(hex >= 'a' && hex <= 'f') ||
|
||||
(hex >= 'A' && hex <= 'F'));
|
||||
assert((hex >= '0' && hex <= '9') || (hex >= 'a' && hex <= 'f') || (hex >= 'A' && hex <= 'F'));
|
||||
|
||||
if (hex >= '0' && hex <= '9')
|
||||
return hex - '0';
|
||||
|
@ -54,20 +53,81 @@ conf_to_color(const struct yml_node *node)
|
|||
uint16_t alpha = hex_byte(&hex[6]);
|
||||
|
||||
alpha |= alpha << 8;
|
||||
int alpha_div = 0xffff / alpha;
|
||||
|
||||
return (pixman_color_t){
|
||||
.red = (red << 8 | red) / alpha_div,
|
||||
.green = (green << 8 | green) / alpha_div,
|
||||
.blue = (blue << 8 | blue) / alpha_div,
|
||||
.red = (uint32_t)(red << 8 | red) * alpha / 0xffff,
|
||||
.green = (uint32_t)(green << 8 | green) * alpha / 0xffff,
|
||||
.blue = (uint32_t)(blue << 8 | blue) * alpha / 0xffff,
|
||||
.alpha = alpha,
|
||||
};
|
||||
}
|
||||
|
||||
struct font *
|
||||
struct fcft_font *
|
||||
conf_to_font(const struct yml_node *node)
|
||||
{
|
||||
return font_from_name(1, &(const char *){yml_value_as_string(node)}, NULL);
|
||||
const char *font_spec = yml_value_as_string(node);
|
||||
|
||||
size_t count = 0;
|
||||
size_t size = 0;
|
||||
const char **fonts = NULL;
|
||||
|
||||
char *copy = strdup(font_spec);
|
||||
for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) {
|
||||
/* Trim spaces, strictly speaking not necessary, but looks nice :) */
|
||||
while (isspace(font[0]))
|
||||
font++;
|
||||
|
||||
if (font[0] == '\0')
|
||||
continue;
|
||||
|
||||
if (count + 1 > size) {
|
||||
size += 4;
|
||||
fonts = realloc(fonts, size * sizeof(fonts[0]));
|
||||
}
|
||||
|
||||
assert(count + 1 <= size);
|
||||
fonts[count++] = font;
|
||||
}
|
||||
|
||||
struct fcft_font *ret = fcft_from_name(count, fonts, NULL);
|
||||
|
||||
free(fonts);
|
||||
free(copy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum font_shaping
|
||||
conf_to_font_shaping(const struct yml_node *node)
|
||||
{
|
||||
const char *v = yml_value_as_string(node);
|
||||
|
||||
if (strcmp(v, "none") == 0)
|
||||
return FONT_SHAPE_NONE;
|
||||
|
||||
else if (strcmp(v, "graphemes") == 0) {
|
||||
static bool have_warned = false;
|
||||
|
||||
if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_GRAPHEME_SHAPING)) {
|
||||
have_warned = true;
|
||||
LOG_WARN("cannot enable grapheme shaping; no support in fcft");
|
||||
}
|
||||
return FONT_SHAPE_GRAPHEMES;
|
||||
}
|
||||
|
||||
else if (strcmp(v, "full") == 0) {
|
||||
static bool have_warned = false;
|
||||
|
||||
if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING)) {
|
||||
have_warned = true;
|
||||
LOG_WARN("cannot enable full text shaping; no support in fcft");
|
||||
}
|
||||
return FONT_SHAPE_FULL;
|
||||
}
|
||||
|
||||
else {
|
||||
assert(false);
|
||||
return FONT_SHAPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
struct deco *
|
||||
|
@ -85,25 +145,20 @@ conf_to_deco(const struct yml_node *node)
|
|||
}
|
||||
|
||||
static struct particle *
|
||||
particle_simple_list_from_config(const struct yml_node *node,
|
||||
struct conf_inherit inherited)
|
||||
particle_simple_list_from_config(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
size_t count = yml_list_length(node);
|
||||
struct particle *parts[count];
|
||||
|
||||
size_t idx = 0;
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
parts[idx] = conf_to_particle(it.node, inherited);
|
||||
}
|
||||
|
||||
/* Lazy-loaded function pointer to particle_list_new() */
|
||||
static struct particle *(*particle_list_new)(
|
||||
struct particle *common,
|
||||
struct particle *particles[], size_t count,
|
||||
int left_spacing, int right_spacing) = NULL;
|
||||
static struct particle *(*particle_list_new)(struct particle *common, struct particle *particles[], size_t count,
|
||||
int left_spacing, int right_spacing)
|
||||
= NULL;
|
||||
|
||||
if (particle_list_new == NULL) {
|
||||
const struct plugin *plug = plugin_load("list", PLUGIN_PARTICLE);
|
||||
|
@ -112,8 +167,8 @@ particle_simple_list_from_config(const struct yml_node *node,
|
|||
assert(particle_list_new != NULL);
|
||||
}
|
||||
|
||||
struct particle *common = particle_common_new(
|
||||
0, 0, NULL, font_clone(inherited.font), inherited.foreground, NULL);
|
||||
struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping,
|
||||
inherited.foreground, NULL);
|
||||
|
||||
return particle_list_new(common, parts, count, 0, 2);
|
||||
}
|
||||
|
@ -132,16 +187,74 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *right_margin = yml_get_value(pair.value, "right-margin");
|
||||
const struct yml_node *on_click = yml_get_value(pair.value, "on-click");
|
||||
const struct yml_node *font_node = yml_get_value(pair.value, "font");
|
||||
const struct yml_node *font_shaping_node = yml_get_value(pair.value, "font-shaping");
|
||||
const struct yml_node *foreground_node = yml_get_value(pair.value, "foreground");
|
||||
const struct yml_node *deco_node = yml_get_value(pair.value, "deco");
|
||||
|
||||
int left = margin != NULL ? yml_value_as_int(margin) :
|
||||
left_margin != NULL ? yml_value_as_int(left_margin) : 0;
|
||||
int right = margin != NULL ? yml_value_as_int(margin) :
|
||||
right_margin != NULL ? yml_value_as_int(right_margin) : 0;
|
||||
int left = margin != NULL ? yml_value_as_int(margin) : left_margin != NULL ? yml_value_as_int(left_margin) : 0;
|
||||
int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0;
|
||||
|
||||
char *on_click_templates[MOUSE_BTN_COUNT] = {NULL};
|
||||
if (on_click != NULL) {
|
||||
const char *yml_legacy = yml_value_as_string(on_click);
|
||||
|
||||
if (yml_legacy != NULL) {
|
||||
char *legacy = NULL;
|
||||
|
||||
if (yml_legacy[0] == '~' && yml_legacy[1] == '/') {
|
||||
const char *home_dir = getenv("HOME");
|
||||
|
||||
if (home_dir != NULL)
|
||||
if (asprintf(&legacy, "%s/%s", home_dir, yml_legacy + 2) < 0)
|
||||
legacy = NULL;
|
||||
|
||||
if (legacy == NULL)
|
||||
legacy = strdup(yml_legacy);
|
||||
} else
|
||||
legacy = strdup(yml_legacy);
|
||||
|
||||
on_click_templates[MOUSE_BTN_LEFT] = legacy;
|
||||
}
|
||||
|
||||
else if (yml_is_dict(on_click)) {
|
||||
for (struct yml_dict_iter it = yml_dict_iter(on_click); it.key != NULL; yml_dict_next(&it)) {
|
||||
const char *key = yml_value_as_string(it.key);
|
||||
const char *yml_template = yml_value_as_string(it.value);
|
||||
|
||||
char *template = NULL;
|
||||
|
||||
if (yml_template[0] == '~' && yml_template[1] == '/') {
|
||||
const char *home_dir = getenv("HOME");
|
||||
|
||||
if (home_dir != NULL)
|
||||
if (asprintf(&template, "%s/%s", home_dir, yml_template + 2) < 0)
|
||||
template = NULL;
|
||||
|
||||
if (template == NULL)
|
||||
template = strdup(yml_template);
|
||||
} else
|
||||
template = strdup(yml_template);
|
||||
|
||||
if (strcmp(key, "left") == 0)
|
||||
on_click_templates[MOUSE_BTN_LEFT] = template;
|
||||
else if (strcmp(key, "middle") == 0)
|
||||
on_click_templates[MOUSE_BTN_MIDDLE] = template;
|
||||
else if (strcmp(key, "right") == 0)
|
||||
on_click_templates[MOUSE_BTN_RIGHT] = template;
|
||||
else if (strcmp(key, "wheel-up") == 0)
|
||||
on_click_templates[MOUSE_BTN_WHEEL_UP] = template;
|
||||
else if (strcmp(key, "wheel-down") == 0)
|
||||
on_click_templates[MOUSE_BTN_WHEEL_DOWN] = template;
|
||||
else if (strcmp(key, "previous") == 0)
|
||||
on_click_templates[MOUSE_BTN_PREVIOUS] = template;
|
||||
else if (strcmp(key, "next") == 0)
|
||||
on_click_templates[MOUSE_BTN_NEXT] = template;
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *on_click_template
|
||||
= on_click != NULL ? yml_value_as_string(on_click) : NULL;
|
||||
struct deco *deco = deco_node != NULL ? conf_to_deco(deco_node) : NULL;
|
||||
|
||||
/*
|
||||
|
@ -153,14 +266,14 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
|
|||
* clone the font, since each particle takes ownership of its own
|
||||
* font.
|
||||
*/
|
||||
struct font *font = font_node != NULL
|
||||
? conf_to_font(font_node) : font_clone(inherited.font);
|
||||
pixman_color_t foreground = foreground_node != NULL
|
||||
? conf_to_color(foreground_node) : inherited.foreground;
|
||||
struct fcft_font *font = font_node != NULL ? conf_to_font(font_node) : fcft_clone(inherited.font);
|
||||
enum font_shaping font_shaping
|
||||
= font_shaping_node != NULL ? conf_to_font_shaping(font_shaping_node) : inherited.font_shaping;
|
||||
pixman_color_t foreground = foreground_node != NULL ? conf_to_color(foreground_node) : inherited.foreground;
|
||||
|
||||
/* Instantiate base/common particle */
|
||||
struct particle *common = particle_common_new(
|
||||
left, right, on_click_template, font, foreground, deco);
|
||||
struct particle *common
|
||||
= particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco);
|
||||
|
||||
const struct particle_iface *iface = plugin_load_particle(type);
|
||||
|
||||
|
@ -176,6 +289,8 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
|
||||
struct bar_config conf = {
|
||||
.backend = backend,
|
||||
.layer = BAR_LAYER_BOTTOM,
|
||||
.font_shaping = FONT_SHAPE_FULL,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -186,8 +301,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
conf.height = yml_value_as_int(height);
|
||||
|
||||
const struct yml_node *location = yml_get_value(bar, "location");
|
||||
conf.location = strcmp(yml_value_as_string(location), "top") == 0
|
||||
? BAR_TOP : BAR_BOTTOM;
|
||||
conf.location = strcmp(yml_value_as_string(location), "top") == 0 ? BAR_TOP : BAR_BOTTOM;
|
||||
|
||||
const struct yml_node *background = yml_get_value(bar, "background");
|
||||
conf.background = conf_to_color(background);
|
||||
|
@ -200,6 +314,21 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
if (monitor != NULL)
|
||||
conf.monitor = yml_value_as_string(monitor);
|
||||
|
||||
const struct yml_node *layer = yml_get_value(bar, "layer");
|
||||
if (layer != NULL) {
|
||||
const char *tmp = yml_value_as_string(layer);
|
||||
if (strcmp(tmp, "overlay") == 0)
|
||||
conf.layer = BAR_LAYER_OVERLAY;
|
||||
else if (strcmp(tmp, "top") == 0)
|
||||
conf.layer = BAR_LAYER_TOP;
|
||||
else if (strcmp(tmp, "bottom") == 0)
|
||||
conf.layer = BAR_LAYER_BOTTOM;
|
||||
else if (strcmp(tmp, "background") == 0)
|
||||
conf.layer = BAR_LAYER_BACKGROUND;
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
|
||||
const struct yml_node *spacing = yml_get_value(bar, "spacing");
|
||||
if (spacing != NULL)
|
||||
conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing);
|
||||
|
@ -224,9 +353,16 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
if (right_margin != NULL)
|
||||
conf.right_margin = yml_value_as_int(right_margin);
|
||||
|
||||
const struct yml_node *trackpad_sensitivity = yml_get_value(bar, "trackpad-sensitivity");
|
||||
conf.trackpad_sensitivity = trackpad_sensitivity != NULL ? yml_value_as_int(trackpad_sensitivity) : 30;
|
||||
|
||||
const struct yml_node *border = yml_get_value(bar, "border");
|
||||
if (border != NULL) {
|
||||
const struct yml_node *width = yml_get_value(border, "width");
|
||||
const struct yml_node *left_width = yml_get_value(border, "left-width");
|
||||
const struct yml_node *right_width = yml_get_value(border, "right-width");
|
||||
const struct yml_node *top_width = yml_get_value(border, "top-width");
|
||||
const struct yml_node *bottom_width = yml_get_value(border, "bottom-width");
|
||||
const struct yml_node *color = yml_get_value(border, "color");
|
||||
const struct yml_node *margin = yml_get_value(border, "margin");
|
||||
const struct yml_node *left_margin = yml_get_value(border, "left-margin");
|
||||
|
@ -235,16 +371,24 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
const struct yml_node *bottom_margin = yml_get_value(border, "bottom-margin");
|
||||
|
||||
if (width != NULL)
|
||||
conf.border.width = yml_value_as_int(width);
|
||||
conf.border.left_width = conf.border.right_width = conf.border.top_width = conf.border.bottom_width
|
||||
= yml_value_as_int(width);
|
||||
|
||||
if (left_width != NULL)
|
||||
conf.border.left_width = yml_value_as_int(left_width);
|
||||
if (right_width != NULL)
|
||||
conf.border.right_width = yml_value_as_int(right_width);
|
||||
if (top_width != NULL)
|
||||
conf.border.top_width = yml_value_as_int(top_width);
|
||||
if (bottom_width != NULL)
|
||||
conf.border.bottom_width = yml_value_as_int(bottom_width);
|
||||
|
||||
if (color != NULL)
|
||||
conf.border.color = conf_to_color(color);
|
||||
|
||||
if (margin != NULL)
|
||||
conf.border.left_margin =
|
||||
conf.border.right_margin =
|
||||
conf.border.top_margin =
|
||||
conf.border.bottom_margin = yml_value_as_int(margin);
|
||||
conf.border.left_margin = conf.border.right_margin = conf.border.top_margin = conf.border.bottom_margin
|
||||
= yml_value_as_int(margin);
|
||||
|
||||
if (left_margin != NULL)
|
||||
conf.border.left_margin = yml_value_as_int(left_margin);
|
||||
|
@ -263,21 +407,27 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
* and particles. This allows us to specify a default font and
|
||||
* foreground color at top-level.
|
||||
*/
|
||||
struct font *font = font_from_name(1, &(const char *){"sans"}, NULL);
|
||||
struct fcft_font *font = fcft_from_name(1, &(const char *){"sans"}, NULL);
|
||||
enum font_shaping font_shaping = FONT_SHAPE_FULL;
|
||||
pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */
|
||||
|
||||
const struct yml_node *font_node = yml_get_value(bar, "font");
|
||||
if (font_node != NULL) {
|
||||
font_destroy(font);
|
||||
fcft_destroy(font);
|
||||
font = conf_to_font(font_node);
|
||||
}
|
||||
|
||||
const struct yml_node *font_shaping_node = yml_get_value(bar, "font-shaping");
|
||||
if (font_shaping_node != NULL)
|
||||
font_shaping = conf_to_font_shaping(font_shaping_node);
|
||||
|
||||
const struct yml_node *foreground_node = yml_get_value(bar, "foreground");
|
||||
if (foreground_node != NULL)
|
||||
foreground = conf_to_color(foreground_node);
|
||||
|
||||
struct conf_inherit inherited = {
|
||||
.font = font,
|
||||
.font_shaping = font_shaping,
|
||||
.foreground = foreground,
|
||||
};
|
||||
|
||||
|
@ -293,10 +443,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
struct module **mods = calloc(count, sizeof(*mods));
|
||||
|
||||
size_t idx = 0;
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
struct yml_dict_iter m = yml_dict_iter(it.node);
|
||||
const char *mod_name = yml_value_as_string(m.key);
|
||||
|
||||
|
@ -307,14 +454,14 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
* applied to all its particles.
|
||||
*/
|
||||
const struct yml_node *mod_font = yml_get_value(m.value, "font");
|
||||
const struct yml_node *mod_foreground = yml_get_value(
|
||||
m.value, "foreground");
|
||||
const struct yml_node *mod_font_shaping = yml_get_value(m.value, "font-shaping");
|
||||
const struct yml_node *mod_foreground = yml_get_value(m.value, "foreground");
|
||||
|
||||
struct conf_inherit mod_inherit = {
|
||||
.font = mod_font != NULL
|
||||
? conf_to_font(mod_font) : inherited.font,
|
||||
.foreground = mod_foreground != NULL
|
||||
? conf_to_color(mod_foreground) : inherited.foreground,
|
||||
.font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font,
|
||||
.font_shaping
|
||||
= mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping,
|
||||
.foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground,
|
||||
};
|
||||
|
||||
const struct module_iface *iface = plugin_load_module(mod_name);
|
||||
|
@ -339,7 +486,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
free(conf.left.mods);
|
||||
free(conf.center.mods);
|
||||
free(conf.right.mods);
|
||||
font_destroy(font);
|
||||
fcft_destroy(font);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
14
config.h
14
config.h
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <fcft/fcft.h>
|
||||
#include "yml.h"
|
||||
#include "bar/bar.h"
|
||||
#include "font-shaping.h"
|
||||
#include "yml.h"
|
||||
#include <fcft/fcft.h>
|
||||
|
||||
struct bar;
|
||||
struct particle;
|
||||
|
@ -15,13 +16,14 @@ struct bar *conf_to_bar(const struct yml_node *bar, enum bar_backend backend);
|
|||
*/
|
||||
|
||||
pixman_color_t conf_to_color(const struct yml_node *node);
|
||||
struct font *conf_to_font(const struct yml_node *node);
|
||||
struct fcft_font *conf_to_font(const struct yml_node *node);
|
||||
enum font_shaping conf_to_font_shaping(const struct yml_node *node);
|
||||
|
||||
struct conf_inherit {
|
||||
const struct font *font;
|
||||
const struct fcft_font *font;
|
||||
enum font_shaping font_shaping;
|
||||
pixman_color_t foreground;
|
||||
};
|
||||
|
||||
struct particle *conf_to_particle(
|
||||
const struct yml_node *node, struct conf_inherit inherited);
|
||||
struct particle *conf_to_particle(const struct yml_node *node, struct conf_inherit inherited);
|
||||
struct deco *conf_to_deco(const struct yml_node *node);
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
struct deco {
|
||||
void *private;
|
||||
void (*expose)(const struct deco *deco, pixman_image_t *pix,
|
||||
int x, int y, int width, int height);
|
||||
void (*expose)(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height);
|
||||
void (*destroy)(struct deco *deco);
|
||||
};
|
||||
|
||||
#define DECORATION_COMMON_ATTRS \
|
||||
{NULL, false, NULL}
|
||||
#define DECORATION_COMMON_ATTRS \
|
||||
{ \
|
||||
NULL, false, NULL \
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
//struct rgba color;
|
||||
struct private
|
||||
{
|
||||
// struct rgba color;
|
||||
pixman_color_t color;
|
||||
};
|
||||
|
||||
|
@ -22,9 +23,7 @@ static void
|
|||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &d->color, 1,
|
||||
&(pixman_rectangle16_t){x, y, width, height});
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, height});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
|
|
91
decorations/border.c
Normal file
91
decorations/border.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#define LOG_MODULE "border"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
struct private
|
||||
{
|
||||
pixman_color_t color;
|
||||
int size;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct deco *deco)
|
||||
{
|
||||
struct private *d = deco->private;
|
||||
free(d);
|
||||
free(deco);
|
||||
}
|
||||
|
||||
static void
|
||||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
/* Top */
|
||||
{x, y, width, min(d->size, height)},
|
||||
|
||||
/* Bottom */
|
||||
{x, max(y + height - d->size, y), width, min(d->size, height)},
|
||||
|
||||
/* Left */
|
||||
{x, y, min(d->size, width), height},
|
||||
|
||||
/* Right */
|
||||
{max(x + width - d->size, x), y, min(d->size, width), height},
|
||||
});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
border_new(pixman_color_t color, int size)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->color = color;
|
||||
priv->size = size;
|
||||
|
||||
struct deco *deco = calloc(1, sizeof(*deco));
|
||||
deco->private = priv;
|
||||
deco->expose = &expose;
|
||||
deco->destroy = &destroy;
|
||||
|
||||
return deco;
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
from_conf(const struct yml_node *node)
|
||||
{
|
||||
const struct yml_node *color = yml_get_value(node, "color");
|
||||
const struct yml_node *size = yml_get_value(node, "size");
|
||||
return border_new(conf_to_color(color), size != NULL ? yml_value_as_int(size) : 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"color", true, &conf_verify_color},
|
||||
{"size", false, &conf_verify_unsigned},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct deco_iface deco_border_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct deco_iface iface __attribute__((weak, alias("deco_border_iface")));
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
|
||||
|
||||
decorations = []
|
||||
foreach deco : ['background', 'stack', 'underline']
|
||||
foreach deco : ['background', 'border', 'stack', 'underline', 'overline']
|
||||
if plugs_as_libs
|
||||
shared_module('@0@'.format(deco), '@0@.c'.format(deco),
|
||||
dependencies: deco_sdk,
|
||||
|
|
71
decorations/overline.c
Normal file
71
decorations/overline.c
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private
|
||||
{
|
||||
int size;
|
||||
pixman_color_t color;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct deco *deco)
|
||||
{
|
||||
struct private *d = deco->private;
|
||||
free(d);
|
||||
free(deco);
|
||||
}
|
||||
|
||||
static void
|
||||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, d->size});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
overline_new(int size, pixman_color_t color)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->size = size;
|
||||
priv->color = color;
|
||||
|
||||
struct deco *deco = calloc(1, sizeof(*deco));
|
||||
deco->private = priv;
|
||||
deco->expose = &expose;
|
||||
deco->destroy = &destroy;
|
||||
|
||||
return deco;
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
from_conf(const struct yml_node *node)
|
||||
{
|
||||
const struct yml_node *size = yml_get_value(node, "size");
|
||||
const struct yml_node *color = yml_get_value(node, "color");
|
||||
return overline_new(yml_value_as_int(size), conf_to_color(color));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"size", true, &conf_verify_unsigned},
|
||||
{"color", true, &conf_verify_color},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct deco_iface deco_overline_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct deco_iface iface __attribute__((weak, alias("deco_overline_iface")));
|
||||
#endif
|
|
@ -1,13 +1,14 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#define LOG_MODULE "stack"
|
||||
#include "../log.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
struct deco **decos;
|
||||
size_t count;
|
||||
};
|
||||
|
@ -57,10 +58,7 @@ from_conf(const struct yml_node *node)
|
|||
struct deco *decos[count];
|
||||
size_t idx = 0;
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
decos[idx] = conf_to_deco(it.node);
|
||||
}
|
||||
|
||||
|
@ -75,10 +73,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it))
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
|
||||
if (!conf_verify_decoration(chain, it.node))
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
int size;
|
||||
pixman_color_t color;
|
||||
};
|
||||
|
@ -22,9 +23,8 @@ static void
|
|||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &d->color, 1,
|
||||
&(pixman_rectangle16_t){x, y + height - d->size, width, d->size});
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1,
|
||||
&(pixman_rectangle16_t){x, y + height - d->size, width, d->size});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
|
@ -54,7 +54,7 @@ static bool
|
|||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"size", true, &conf_verify_int},
|
||||
{"size", true, &conf_verify_unsigned},
|
||||
{"color", true, &conf_verify_color},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
|
|
@ -1,11 +1,86 @@
|
|||
sh = find_program('sh', native: true)
|
||||
|
||||
scdoc = dependency('scdoc', native: true)
|
||||
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
|
||||
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
|
||||
|
||||
foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
|
||||
'yambar-modules.5.scd', 'yambar-particles.5.scd',
|
||||
'yambar-tags.5.scd']
|
||||
plugin_pages = []
|
||||
if plugin_alsa_enabled
|
||||
plugin_pages += ['yambar-modules-alsa.5.scd']
|
||||
endif
|
||||
if plugin_backlight_enabled
|
||||
plugin_pages += ['yambar-modules-backlight.5.scd']
|
||||
endif
|
||||
if plugin_battery_enabled
|
||||
plugin_pages += ['yambar-modules-battery.5.scd']
|
||||
endif
|
||||
if plugin_clock_enabled
|
||||
plugin_pages += ['yambar-modules-clock.5.scd']
|
||||
endif
|
||||
if plugin_cpu_enabled
|
||||
plugin_pages += ['yambar-modules-cpu.5.scd']
|
||||
endif
|
||||
if plugin_disk_io_enabled
|
||||
plugin_pages += ['yambar-modules-disk-io.5.scd']
|
||||
endif
|
||||
if plugin_dwl_enabled
|
||||
plugin_pages += ['yambar-modules-dwl.5.scd']
|
||||
endif
|
||||
if plugin_foreign_toplevel_enabled
|
||||
plugin_pages += ['yambar-modules-foreign-toplevel.5.scd']
|
||||
endif
|
||||
if plugin_mem_enabled
|
||||
plugin_pages += ['yambar-modules-mem.5.scd']
|
||||
endif
|
||||
if plugin_mpd_enabled
|
||||
plugin_pages += ['yambar-modules-mpd.5.scd']
|
||||
endif
|
||||
if plugin_mpris_enabled
|
||||
plugin_pages += ['yambar-modules-mpris.5.scd']
|
||||
endif
|
||||
if plugin_i3_enabled
|
||||
plugin_pages += ['yambar-modules-i3.5.scd']
|
||||
plugin_pages += ['yambar-modules-sway.5.scd']
|
||||
endif
|
||||
if plugin_label_enabled
|
||||
plugin_pages += ['yambar-modules-label.5.scd']
|
||||
endif
|
||||
if plugin_network_enabled
|
||||
plugin_pages += ['yambar-modules-network.5.scd']
|
||||
endif
|
||||
if plugin_niri_language_enabled
|
||||
plugin_pages += ['yambar-modules-niri-language.5.scd']
|
||||
endif
|
||||
if plugin_niri_workspaces_enabled
|
||||
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
|
||||
endif
|
||||
if plugin_pipewire_enabled
|
||||
plugin_pages += ['yambar-modules-pipewire.5.scd']
|
||||
endif
|
||||
if plugin_pulse_enabled
|
||||
plugin_pages += ['yambar-modules-pulse.5.scd']
|
||||
endif
|
||||
if plugin_removables_enabled
|
||||
plugin_pages += ['yambar-modules-removables.5.scd']
|
||||
endif
|
||||
if plugin_river_enabled
|
||||
plugin_pages += ['yambar-modules-river.5.scd']
|
||||
endif
|
||||
if plugin_script_enabled
|
||||
plugin_pages += ['yambar-modules-script.5.scd']
|
||||
endif
|
||||
if plugin_sway_xkb_enabled
|
||||
plugin_pages += ['yambar-modules-sway-xkb.5.scd']
|
||||
endif
|
||||
if plugin_xkb_enabled
|
||||
plugin_pages += ['yambar-modules-xkb.5.scd']
|
||||
endif
|
||||
|
||||
foreach man_src : ['yambar.1.scd',
|
||||
'yambar.5.scd',
|
||||
'yambar-decorations.5.scd',
|
||||
'yambar-modules.5.scd',
|
||||
'yambar-particles.5.scd',
|
||||
'yambar-tags.5.scd'] + plugin_pages
|
||||
parts = man_src.split('.')
|
||||
name = parts[-3]
|
||||
section = parts[-2]
|
||||
|
@ -15,7 +90,7 @@ foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
|
|||
out,
|
||||
output: out,
|
||||
input: man_src,
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
|
||||
capture: true,
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
|
||||
|
|
|
@ -23,7 +23,7 @@ This decoration sets the particles background color.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
|
@ -49,7 +49,7 @@ bottom of the particle.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| size
|
||||
: int
|
||||
: yes
|
||||
|
@ -70,9 +70,74 @@ content:
|
|||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# OVERLINE
|
||||
|
||||
Similar to _underline_, this decoration renders a line of configurable
|
||||
size and color at the top of the particle.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| size
|
||||
: int
|
||||
: yes
|
||||
: The size (height/thickness) of the line, in pixels
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
: The color of the line. See *yambar*(5) for format.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
string:
|
||||
deco:
|
||||
overline:
|
||||
size: 2
|
||||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# BORDER
|
||||
|
||||
This decoration renders a border of configurable size (i.e border
|
||||
width) around the particle.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
: The color of the line. See *yambar*(5) for format.
|
||||
| size
|
||||
: int
|
||||
: no
|
||||
: Border width, in pixels. Defaults to 1px.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
string:
|
||||
deco:
|
||||
border:
|
||||
size: 2
|
||||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# STACK
|
||||
|
||||
This particles combines multiple decorations.
|
||||
This particle combines multiple decorations.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
|
|
70
doc/yambar-modules-alsa.5.scd
Normal file
70
doc/yambar-modules-alsa.5.scd
Normal file
|
@ -0,0 +1,70 @@
|
|||
yambar-modules-alsa(5)
|
||||
|
||||
# NAME
|
||||
alsa - Monitors an alsa soundcard for volume and mute/unmute changes
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| online
|
||||
: bool
|
||||
: True when the ALSA device has successfully been opened
|
||||
| dB
|
||||
: range
|
||||
: Volume level (in dB), with min and max as start and end range
|
||||
values.
|
||||
| volume
|
||||
: range
|
||||
: Volume level (raw), with min and max as start and end range values
|
||||
| percent
|
||||
: range
|
||||
: Volume level, as a percentage. This value is based on the *dB* tag
|
||||
if available, otherwise the *volume* tag.
|
||||
| muted
|
||||
: bool
|
||||
: True if muted, otherwise false
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| card
|
||||
: string
|
||||
: yes
|
||||
: The soundcard name. *default* might work.
|
||||
| mixer
|
||||
: string
|
||||
: yes
|
||||
: Mixer channel to monitor. _Master_ might work.
|
||||
| volume
|
||||
: string
|
||||
: no
|
||||
: The name of the channel to use as source for the volume level
|
||||
(default: first available channel, usually "Front Left").
|
||||
| muted
|
||||
: string
|
||||
: no
|
||||
: The name of the channel to use as source for the muted state
|
||||
(default: first available channel, usually "Front Left").
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- alsa:
|
||||
card: hw:PCH
|
||||
mixer: Master
|
||||
content: {string: {text: "{volume}"}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
47
doc/yambar-modules-backlight.5.scd
Normal file
47
doc/yambar-modules-backlight.5.scd
Normal file
|
@ -0,0 +1,47 @@
|
|||
yambar-modules-backlight(5)
|
||||
|
||||
# NAME
|
||||
backlight - This module reads monitor backlight status
|
||||
|
||||
# DESCRIPTION
|
||||
This module reads monitor backlight status from
|
||||
_/sys/class/backlight_, and uses *udev* to monitor for changes.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| brightness
|
||||
: range
|
||||
: The current brightness level, in absolute value
|
||||
| percent
|
||||
: range
|
||||
: The current brightness level, in percent
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: yes
|
||||
: The backlight device's name (one of the names in */sys/class/backlight*)
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- backlight:
|
||||
name: intel_backlight
|
||||
content:
|
||||
string: {text: "backlight: {percent}%"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
90
doc/yambar-modules-battery.5.scd
Normal file
90
doc/yambar-modules-battery.5.scd
Normal file
|
@ -0,0 +1,90 @@
|
|||
yambar-modules-battery(5)
|
||||
|
||||
# NAME
|
||||
battery - This module reads battery status
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module reads battery status from _/sys/class/power_supply_ and
|
||||
uses *udev* to monitor for changes.
|
||||
|
||||
Note that it is common (and "normal") for batteries to be in the state
|
||||
*unknown* under certain conditions.
|
||||
|
||||
For example, some have been seen to enter the *unknown* state when
|
||||
charging and the capacity reaches ~90%. The battery then stays in
|
||||
*unknown*, rather than *charging*, until it has been fully charged and
|
||||
enters the state *full*.
|
||||
|
||||
This does not happen with all batteries, and other batteries may enter
|
||||
the state *unknown* under other conditions.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: Battery device name
|
||||
| manufacturer
|
||||
: string
|
||||
: Name of the battery manufacturer
|
||||
| model
|
||||
: string
|
||||
: Battery model name
|
||||
| state
|
||||
: string
|
||||
: One of *full*, *not charging*, *charging*, *discharging* or *unknown*
|
||||
| capacity
|
||||
: range
|
||||
: capacity left, in percent
|
||||
| estimate
|
||||
: string
|
||||
: Estimated time left (to empty while discharging, or to full while
|
||||
charging), formatted as HH:MM.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: yes
|
||||
: Battery device name (one of the names in */sys/class/power_supply*)
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: How often, in milliseconds, to poll for capacity changes
|
||||
(default=*60000*). Set to `0` to disable polling (*warning*: many
|
||||
batteries do not support asynchronous reporting). Cannot be less
|
||||
than 250ms.
|
||||
| battery-scale
|
||||
: int
|
||||
: no
|
||||
: How much to scale down the battery charge amount. Some batteries
|
||||
report too high resulting in bad discharge estimates. Default=1.
|
||||
| smoothing-secs
|
||||
: int
|
||||
: no
|
||||
: How many seconds to perform smoothing over for battery discharge
|
||||
estimates. Default=100s.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30000
|
||||
content:
|
||||
string: {text: "BAT: {capacity}% {estimate}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
51
doc/yambar-modules-clock.5.scd
Normal file
51
doc/yambar-modules-clock.5.scd
Normal file
|
@ -0,0 +1,51 @@
|
|||
yambar-modules-clock(5)
|
||||
|
||||
# NAME
|
||||
clock - This module provides the current date and time
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| time
|
||||
: string
|
||||
: Current time, formatted using the _time-format_ attribute
|
||||
| date
|
||||
: string
|
||||
: Current date, formatted using the _date-format_ attribute
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| time-format
|
||||
: string
|
||||
: no
|
||||
: *strftime* formatter for the _time_ tag (default=*%H:%M*)
|
||||
| date-format
|
||||
: string
|
||||
: no
|
||||
: *strftime* formatter for the _date_ date (default=*%x*)
|
||||
| utc
|
||||
: bool
|
||||
: no
|
||||
: Use GMT instead of local timezone (default=false)
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- clock:
|
||||
time-format: "%H:%M %Z"
|
||||
content:
|
||||
string: {text: "{date} {time}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
79
doc/yambar-modules-cpu.5.scd
Normal file
79
doc/yambar-modules-cpu.5.scd
Normal file
|
@ -0,0 +1,79 @@
|
|||
yambar-modules-cpu(5)
|
||||
|
||||
# NAME
|
||||
cpu - This module provides the CPU usage
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module reports CPU usage, in percent. The _content_ particle is a
|
||||
template that is instantiated once for each core, and once for the
|
||||
total CPU usage.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: Core ID. 0..n represents individual cores, and -1 represents the
|
||||
total usage
|
||||
| cpu
|
||||
: range
|
||||
: Current usage of CPU core {id}, in percent
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: Refresh interval of the CPU usage stats in milliseconds
|
||||
(default=500). Cannot be less then 250ms.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Display total CPU usage as a number
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- cpu:
|
||||
poll-interval: 2500
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
id < 0:
|
||||
- string: {text: , font: Font Awesome 6 Free:style=solid}
|
||||
- string: {text: "{cpu}%"}
|
||||
```
|
||||
|
||||
## Display a vertical bar for each core
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- cpu:
|
||||
poll-interval: 2500
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
id >= 0:
|
||||
- ramp:
|
||||
tag: cpu
|
||||
items:
|
||||
- string: {text: ▁}
|
||||
- string: {text: ▂}
|
||||
- string: {text: ▃}
|
||||
- string: {text: ▄}
|
||||
- string: {text: ▅}
|
||||
- string: {text: ▆}
|
||||
- string: {text: ▇}
|
||||
- string: {text: █}
|
||||
```
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
85
doc/yambar-modules-disk-io.5.scd
Normal file
85
doc/yambar-modules-disk-io.5.scd
Normal file
|
@ -0,0 +1,85 @@
|
|||
yambar-modules-disk-io(5)
|
||||
|
||||
# NAME
|
||||
disk-io - This module keeps track of the amount of bytes being
|
||||
read/written from/to disk. It can distinguish between all partitions
|
||||
currently present in the machine.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| device
|
||||
: string
|
||||
: Name of the device being tracked (use the command *lsblk* to see these).
|
||||
There is a special device, "Total", that reports the total stats
|
||||
for the machine
|
||||
| is_disk
|
||||
: boolean
|
||||
: whether or not the device is a disk (e.g., sda, sdb) or a partition
|
||||
(e.g., sda1, sda2, ...). "Total" is advertised as a disk.
|
||||
| read_speed
|
||||
: int
|
||||
: bytes read, in bytes/s
|
||||
| write_speed
|
||||
: int
|
||||
: bytes written, in bytes/s
|
||||
| ios_in_progress
|
||||
: int
|
||||
: number of ios that are happening at the time of polling
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: Refresh interval of disk's stats in milliseconds (default=500).
|
||||
Cannot be less then 250ms.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
This reports the total amount of bytes being read and written every second,
|
||||
formatting in b/s, kb/s, mb/s, or gb/s, as appropriate.
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- disk-io:
|
||||
poll-interval: 1000
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
device == Total:
|
||||
list:
|
||||
items:
|
||||
- string: {text: "Total read: "}
|
||||
- map:
|
||||
default: {string: {text: "{read_speed} B/s"}}
|
||||
conditions:
|
||||
read_speed > 1073741824:
|
||||
string: {text: "{read_speed:gib} GB/s"}
|
||||
read_speed > 1048576:
|
||||
string: {text: "{read_speed:mib} MB/s"}
|
||||
read_speed > 1024:
|
||||
string: {text: "{read_speed:kib} KB/s"}
|
||||
- string: {text: " | "}
|
||||
- string: {text: "Total written: "}
|
||||
- map:
|
||||
default: {string: {text: "{write_speed} B/s"}}
|
||||
conditions:
|
||||
write_speed > 1073741824:
|
||||
string: {text: "{write_speed:gib} GB/s"}
|
||||
write_speed > 1048576:
|
||||
string: {text: "{write_speed:mib} MB/s"}
|
||||
write_speed > 1024:
|
||||
string: {text: "{write_speed:kib} KB/s"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
107
doc/yambar-modules-dwl.5.scd
Normal file
107
doc/yambar-modules-dwl.5.scd
Normal file
|
@ -0,0 +1,107 @@
|
|||
yambar-modules-dwl(5)
|
||||
|
||||
# NAME
|
||||
dwl - This module provides information about dwl tags, and information.
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module provides a map of each tags present in dwl.
|
||||
|
||||
Each tags has its _id_, its _name_, its status (_selected_, _empty_, _urgent_)
|
||||
and the global data like _title_, _appid_, _fullscreen_, _floating_,
|
||||
_selmon_, and _layout_). The tags start a 1. For needs where
|
||||
you only want information about the global data and not the _tags_,
|
||||
there is a tag with the id _0_ that contains only the global data.
|
||||
|
||||
This module will track *only* the monitor where yambar was launched on.
|
||||
If you have a multi monitor setup, please launch yambar on each of your
|
||||
monitors.
|
||||
|
||||
Please, be aware that only *one instance* of this module is supported.
|
||||
Running multiple instances at the same time may result in
|
||||
*undefined behavior*.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: dwl tag id.
|
||||
| name
|
||||
: string
|
||||
: The name of the tag (defaults to _id_ if not set).
|
||||
| selected
|
||||
: bool
|
||||
: True if the tag is currently selected.
|
||||
| empty
|
||||
: bool
|
||||
: True if there are no windows in the tag.
|
||||
| urgent
|
||||
: bool
|
||||
: True if the tag has the urgent flag set.
|
||||
| title
|
||||
: string
|
||||
: The currently focused window's title.
|
||||
| appid
|
||||
: string
|
||||
: The currently focused window's application id.
|
||||
| fullscreen
|
||||
: bool
|
||||
: True if there is a fullscreen window in the current tag.
|
||||
| floating
|
||||
: bool
|
||||
: True if there is a floating window in the current tag.
|
||||
| selmon
|
||||
: bool
|
||||
: True if the monitor is actually focused.
|
||||
| layout
|
||||
: string
|
||||
: The actual layout name of the tag.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| number-of-tags
|
||||
: int
|
||||
: yes
|
||||
: The number of defined tags in the dwl `config.def.h`.
|
||||
| name-of-tags
|
||||
: list
|
||||
: false
|
||||
: The name of the tags (must have the same length that _number-of-tags_).
|
||||
| dwl-info-filename
|
||||
: string
|
||||
: yes
|
||||
: The filepath to the log emitted by dwl when running.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- dwl:
|
||||
number-of-tags: 9
|
||||
dwl-info-filename: "/home/ogromny/dwl_info"
|
||||
name-of-tags: [ , , , , , , , , ]
|
||||
content:
|
||||
list:
|
||||
items:
|
||||
- map:
|
||||
conditions:
|
||||
# default tag
|
||||
id == 0: {string: {text: "{layout} {title}"}}
|
||||
|
||||
selected: {string: {text: "-> {name}"}}
|
||||
~empty: {string: {text: "{name}"}}
|
||||
urgent: {string: {text: "=> {name} <="}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
78
doc/yambar-modules-foreign-toplevel.5.scd
Normal file
78
doc/yambar-modules-foreign-toplevel.5.scd
Normal file
|
@ -0,0 +1,78 @@
|
|||
yambar-modules-foreign-toplevel(5)
|
||||
|
||||
# NAME
|
||||
foreign-toplevel - This module provides information about toplevel windows in Wayland
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module uses the _wlr foreign toplevel management_ Wayland
|
||||
protocol to provide information about currently open windows, such as
|
||||
their application ID, window title, and their current state
|
||||
(maximized/minimized/fullscreen/activated).
|
||||
|
||||
The configuration for the foreign-toplevel module specifies a
|
||||
_template_ particle. This particle will be instantiated once for each
|
||||
window.
|
||||
|
||||
Note: Wayland only.
|
||||
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| app-id
|
||||
: string
|
||||
: The application ID (typically the application name)
|
||||
| title
|
||||
: string
|
||||
: The window title
|
||||
| maximized
|
||||
: bool
|
||||
: True if the window is currently maximized
|
||||
| minimized
|
||||
: bool
|
||||
: True if the window is currently minimized
|
||||
| fullscreen
|
||||
: bool
|
||||
: True if the window is currently fullscreened
|
||||
| activated
|
||||
: bool
|
||||
: True if the window is currently activated (i.e. has focus)
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: Template particle that will be instantiated once for each window
|
||||
| all-monitors
|
||||
: bool
|
||||
: no
|
||||
: When set to true, only windows on the same monitor the bar will be
|
||||
used. The default is false.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: "{app-id}: {title}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
117
doc/yambar-modules-i3.5.scd
Normal file
117
doc/yambar-modules-i3.5.scd
Normal file
|
@ -0,0 +1,117 @@
|
|||
yambar-modules-i3(5)
|
||||
|
||||
# NAME
|
||||
i3 - This module monitors i3 and sway workspaces
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Unlike other modules where the _content_ attribute is just a single
|
||||
*particle*, the i3 module's _content_ is an associative array mapping
|
||||
i3/sway workspace names to a particle.
|
||||
|
||||
You can add an empty workspace name, *""*, as a catch-all workspace
|
||||
particle. The *i3* module will fallback to this entry if it cannot
|
||||
find the workspace name in the _content_ map.
|
||||
|
||||
It also recognizes the special name *current*, which always represents
|
||||
the currently focused workspace. On Sway, this can be used together
|
||||
with the _application_ and _title_ tags to replace the X11-only
|
||||
*xwindow* module.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: The workspace name
|
||||
| output
|
||||
: string
|
||||
: The output (monitor) the workspace is on
|
||||
| visible
|
||||
: bool
|
||||
: True if the workspace is currently visible (on any output)
|
||||
| focused
|
||||
: bool
|
||||
: True if the workspace is currently focused
|
||||
| urgent
|
||||
: bool
|
||||
: True if the workspace has the urgent flag set
|
||||
| empty
|
||||
: bool
|
||||
: True if the workspace is empty (Sway only)
|
||||
| state
|
||||
: string
|
||||
: One of *urgent*, *focused*, *unfocused* or *invisible* (note:
|
||||
*unfocused* is when it is visible, but neither focused nor urgent).
|
||||
| application
|
||||
: string
|
||||
: Name of application currently focused on this workspace (Sway only - use the *xwindow* module in i3)
|
||||
| title
|
||||
: string
|
||||
: This workspace's focused window's title
|
||||
| mode
|
||||
: string
|
||||
: The name of the current mode
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| content
|
||||
: associative array
|
||||
: yes
|
||||
: Unlike other modules, _content_ is an associative array mapping
|
||||
workspace names to particles. Use *""* to specify a default
|
||||
fallback particle, or *current* for the currently active workspace.
|
||||
| sort
|
||||
: enum
|
||||
: no
|
||||
: How to sort the list of workspaces; one of _none_, _native_, _ascending_ or _descending_, defaults to _none_. Use _native_ to sort numbered workspaces only.
|
||||
| strip-workspace-numbers
|
||||
: bool
|
||||
: no
|
||||
: If true, *N:* prefixes will be stripped from workspace names. Useful together with *sort*, to have the workspace order fixed.
|
||||
| persistent
|
||||
: list of strings
|
||||
: no
|
||||
: Persistent workspaces. I.e. workspaces that are never removed, even if empty.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the left-side of each rendered workspace particle
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right-side of each rendered workspace particle
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
This renders all workspace names, with an *\** indicating the
|
||||
currently focused one. It also renders the currently focused
|
||||
application name and window title.
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- i3:
|
||||
content:
|
||||
"":
|
||||
map:
|
||||
default: {string: {text: "{name}"}}
|
||||
conditions:
|
||||
state == focused: {string: {text: "{name}*"}}
|
||||
current: { string: {text: "{application}: {title}"}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
32
doc/yambar-modules-label.5.scd
Normal file
32
doc/yambar-modules-label.5.scd
Normal file
|
@ -0,0 +1,32 @@
|
|||
yambar-modules-label(5)
|
||||
|
||||
# NAME
|
||||
label - This module renders the provided _content_ particle
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module renders the provided _content_ particle, but provides no
|
||||
additional data.
|
||||
|
||||
# TAGS
|
||||
|
||||
None
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- label:
|
||||
content: {string: {text: hello world}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
52
doc/yambar-modules-mem.5.scd
Normal file
52
doc/yambar-modules-mem.5.scd
Normal file
|
@ -0,0 +1,52 @@
|
|||
yambar-modules-mem(5)
|
||||
|
||||
# NAME
|
||||
mem - This module provides the memory usage
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| free
|
||||
: int
|
||||
: Free memory in bytes
|
||||
| used
|
||||
: int
|
||||
: Used memory in bytes
|
||||
| total
|
||||
: int
|
||||
: Total memory in bytes
|
||||
| percent_free
|
||||
: range
|
||||
: Free memory in percent
|
||||
| percent_used
|
||||
: range
|
||||
: Used memory in percent
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| poll-interval
|
||||
: string
|
||||
: no
|
||||
: Refresh interval of the memory usage stats in milliseconds
|
||||
(default=500). Cannot be less then 250ms.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- mem:
|
||||
poll-interval: 2500
|
||||
content:
|
||||
string: {text: "{used:mb}MB"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
86
doc/yambar-modules-mpd.5.scd
Normal file
86
doc/yambar-modules-mpd.5.scd
Normal file
|
@ -0,0 +1,86 @@
|
|||
yambar-modules-mpd(5)
|
||||
|
||||
# NAME
|
||||
mpd - This module provides MPD status such as currently playing artist/album/song
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| state
|
||||
: string
|
||||
: One of *offline*, *stopped*, *paused* or *playing*
|
||||
| repeat
|
||||
: bool
|
||||
: True if the *repeat* flag is set
|
||||
| random
|
||||
: bool
|
||||
: True if the *random* flag is set
|
||||
| consume
|
||||
: bool
|
||||
: True if the *consume* flag is set
|
||||
| single
|
||||
: bool
|
||||
: True if the *single* flag is set
|
||||
| volume
|
||||
: range
|
||||
: Volume of MPD in percentage
|
||||
| album
|
||||
: string
|
||||
: Currently playing album (also valid in *paused* state)
|
||||
| artist
|
||||
: string
|
||||
: Artist of currently playing song (also valid in *paused* state)
|
||||
| title
|
||||
: string
|
||||
: Title of currently playing song (also valid in *paused* state)
|
||||
| file
|
||||
: string
|
||||
: Filename or URL of currently playing song (also valid in *paused* state)
|
||||
| pos
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's current position
|
||||
(also see _elapsed_)
|
||||
| end
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's total length (also
|
||||
see _duration_)
|
||||
| elapsed
|
||||
: realtime
|
||||
: Position in currently playing song, in milliseconds. Can be used
|
||||
with a _progress-bar_ particle.
|
||||
| duration
|
||||
: int
|
||||
: Length of currently playing song, in milliseconds
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| host
|
||||
: string
|
||||
: yes
|
||||
: Hostname/IP/unix-socket to connect to
|
||||
| port
|
||||
: int
|
||||
: no
|
||||
: TCP port to connect to
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- mpd:
|
||||
host: /run/mpd/socket
|
||||
content:
|
||||
string: {text: "{artist} - {album} - {title} ({end})"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
101
doc/yambar-modules-mpris.5.scd
Normal file
101
doc/yambar-modules-mpris.5.scd
Normal file
|
@ -0,0 +1,101 @@
|
|||
yambar-modules-mpris(5)
|
||||
|
||||
# NAME
|
||||
mpris - This module provides MPRIS status such as currently playing artist/album/song
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| state
|
||||
: string
|
||||
: One of *offline*, *stopped*, *paused* or *playing*
|
||||
| shuffle
|
||||
: bool
|
||||
: True if the *shuffle* flag is set
|
||||
| repeat
|
||||
: string
|
||||
: One of *none*, *track* or *paylist*
|
||||
| volume
|
||||
: range
|
||||
: Volume in percentage
|
||||
| album
|
||||
: string
|
||||
: Currently playing album
|
||||
| artist
|
||||
: string
|
||||
: Artist of currently playing song
|
||||
| title
|
||||
: string
|
||||
: Title of currently playing song
|
||||
| file
|
||||
: string
|
||||
: Filename or URL of currently playing song
|
||||
| pos
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's current position
|
||||
(also see _elapsed_)
|
||||
| end
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's total length (also
|
||||
see _duration_)
|
||||
| elapsed
|
||||
: realtime
|
||||
: Position in currently playing song, in milliseconds. Can be used
|
||||
with a _progress-bar_ particle.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| identities
|
||||
: list of string
|
||||
: yes
|
||||
: A list of MPRIS client identities
|
||||
| query_timeout
|
||||
: int
|
||||
: no
|
||||
: Dbus/MPRIS client connection timeout in ms. Try setting/incrementing
|
||||
this value if the module reports a timeout error. Defaults to 500.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
center:
|
||||
- mpris:
|
||||
identities:
|
||||
- "spotify"
|
||||
- "firefox"
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
state != offline && state != stopped:
|
||||
- string: {text: "{artist}", max: 30 }
|
||||
- string: {text: "-" }
|
||||
- string: {text: "{title}", max: 30 }
|
||||
```
|
||||
|
||||
# NOTE
|
||||
|
||||
The 'identity' refers a part of your clients DBus bus name.
|
||||
You can obtain a list of active client names using:
|
||||
|
||||
```
|
||||
Systemd: > busctl --user --list
|
||||
Playerctl: > playerctl --list-all
|
||||
Libdbus: > dbus-send --session --print-reply --type=method_call \
|
||||
--dest='org.freedesktop.DBus' /org org.freedesktop.DBus.ListNames
|
||||
```
|
||||
|
||||
MPRIS client bus names start with 'org.mpris.MediaPlayer2.<identity>'.
|
||||
For example, firefox may use the bus name:
|
||||
'org.mpris.MediaPlayer2.firefox.instance_1_7' which
|
||||
gives us the identity 'firefox'
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
126
doc/yambar-modules-network.5.scd
Normal file
126
doc/yambar-modules-network.5.scd
Normal file
|
@ -0,0 +1,126 @@
|
|||
yambar-modules-network(5)
|
||||
|
||||
# NAME
|
||||
network - This module monitors network connection state
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module monitors network connection state; disconnected/connected
|
||||
state and MAC/IP addresses. It instantiates the provided _content_
|
||||
particle for each network interface.
|
||||
|
||||
Note: while the module internally tracks all assigned IPv4/IPv6
|
||||
addresses, it currently exposes only a single IPv4 and a single IPv6
|
||||
address per network interface.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: Network interface name
|
||||
| type
|
||||
: string
|
||||
: Interface type (*ether*, *wlan*, *loopback*, or *ARPHRD_NNN*, where
|
||||
*N* is a number).
|
||||
| kind
|
||||
: string
|
||||
: Interface kind. Empty for non-virtual interfaces. For virtual
|
||||
interfaces, this value is taken from the _IFLA\_INFO\_KIND_ netlink
|
||||
attribute. Examples of valid values are *bond*, *bridge*, *gre*, *tun*
|
||||
and *veth*.
|
||||
| index
|
||||
: int
|
||||
: Network interface index
|
||||
| carrier
|
||||
: bool
|
||||
: True if the interface has CARRIER. That is, if it is physically connected.
|
||||
| state
|
||||
: string
|
||||
: One of *unknown*, *not present*, *down*, *lower layers down*,
|
||||
*testing*, *dormant* or *up*. You are probably interested in *down* and *up*.
|
||||
| mac
|
||||
: string
|
||||
: MAC address
|
||||
| ipv4
|
||||
: string
|
||||
: IPv4 address assigned to the interface, or *""* if none
|
||||
| ipv6
|
||||
: string
|
||||
: IPv6 address assigned to the interface, or *""* if none
|
||||
| ssid
|
||||
: string
|
||||
: SSID the adapter is connected to (Wi-Fi only)
|
||||
| signal
|
||||
: int
|
||||
: Signal strength, in dBm (Wi-Fi only)
|
||||
| quality
|
||||
: range
|
||||
: Quality of the signal, in percent (Wi-Fi only)
|
||||
| rx-bitrate
|
||||
: int
|
||||
: RX bitrate, in bits/s
|
||||
| tx-bitrate
|
||||
: int
|
||||
: TX bitrate in bits/s
|
||||
| dl-speed
|
||||
: int
|
||||
: Download speed in bits/s
|
||||
| ul-speed
|
||||
: int
|
||||
: Upload speed in bits/s
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered volume
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered volume
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: Periodically (in milliseconds) update the signal, quality, rx+tx bitrate, and
|
||||
ul+dl speed tags (default=0). Setting it to 0 disables updates. Cannot be less
|
||||
than 250ms.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
Display all Ethernet (including WLAN) devices. This excludes loopback,
|
||||
bridges etc.
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- network:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
type == ether || type == wlan:
|
||||
map:
|
||||
default:
|
||||
string: {text: "{name}: {state} ({ipv4})"}
|
||||
conditions:
|
||||
ipv4 == "":
|
||||
string: {text: "{name}: {state}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
34
doc/yambar-modules-niri-language.5.scd
Normal file
34
doc/yambar-modules-niri-language.5.scd
Normal file
|
@ -0,0 +1,34 @@
|
|||
yambar-modules-niri-language(5)
|
||||
|
||||
# NAME
|
||||
niri-language - This module provides information about niri's currently
|
||||
selected language.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| language
|
||||
: string
|
||||
: The currently selected language.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- niri-language:
|
||||
content:
|
||||
string: {text: "{language}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
60
doc/yambar-modules-niri-workspaces.5.scd
Normal file
60
doc/yambar-modules-niri-workspaces.5.scd
Normal file
|
@ -0,0 +1,60 @@
|
|||
yambar-modules-niri-workspaces(5)
|
||||
|
||||
# NAME
|
||||
niri-workspaces - This module provides information about niri workspaces.
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module provides a map of each workspace present in niri.
|
||||
|
||||
Each workspace has its _id_, _name_, and its status (_focused_,
|
||||
_active_, _empty_). The workspaces are sorted by their ids.
|
||||
|
||||
This module will *only* track the monitor where yambar was launched.
|
||||
If you have a multi monitor setup, please launch yambar on each
|
||||
individual monitor to track its workspaces.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: The workspace id.
|
||||
| name
|
||||
: string
|
||||
: The name of the workspace.
|
||||
| active
|
||||
: bool
|
||||
: True if the workspace is currently visible on the current output.
|
||||
| focused
|
||||
: bool
|
||||
: True if the workspace is currently focused.
|
||||
| empty
|
||||
: bool
|
||||
: True if the workspace contains no window.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- niri-workspaces:
|
||||
content:
|
||||
map:
|
||||
default: {string: {text: "| {id}"}}
|
||||
conditions:
|
||||
active: {string: {text: "-> {id}"}}
|
||||
~empty: {string: {text: "@ {id}"}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
103
doc/yambar-modules-pipewire.5.scd
Normal file
103
doc/yambar-modules-pipewire.5.scd
Normal file
|
@ -0,0 +1,103 @@
|
|||
yambar-modules-pipewire(5)
|
||||
|
||||
# NAME
|
||||
pipewire - Monitors pipewire for volume, mute/unmute, device change
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| type
|
||||
: string
|
||||
: Either "source" (capture) or "sink" (speaker)
|
||||
| name
|
||||
: string
|
||||
: Current device name
|
||||
| description
|
||||
: string
|
||||
: Current device description
|
||||
| form_factor
|
||||
: string
|
||||
: Current device form factor (headset, speaker, mic, etc.)
|
||||
| bus
|
||||
: string
|
||||
: Current device bus (bluetooth, alsa, etc.)
|
||||
| icon
|
||||
: string
|
||||
: Current device icon name
|
||||
| muted
|
||||
: bool
|
||||
: True if muted, otherwise false
|
||||
| linear_volume
|
||||
: range
|
||||
: Linear volume in percentage (with 0 as min and 100 as max)
|
||||
| cubic_volume
|
||||
: range
|
||||
: Cubic volume (used by pulseaudio) in percentage (with 0 as min and 100 as max)
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered volume
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered volume
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: Unlike other modules, _content_ is a template particle that will be
|
||||
expanded twice (i.e. into a list of two elements). The first
|
||||
element is the 'sink', and the second element the 'source'.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- pipewire:
|
||||
anchors:
|
||||
volume: &volume
|
||||
conditions:
|
||||
muted: {string: {text: "{linear_volume}%", foreground: ff0000ff}}
|
||||
~muted: {string: {text: "{linear_volume}%"}}
|
||||
content:
|
||||
list:
|
||||
items:
|
||||
- map:
|
||||
conditions:
|
||||
type == "sink":
|
||||
map:
|
||||
conditions:
|
||||
icon == "audio-headset-bluetooth":
|
||||
string: {text: "🎧 "}
|
||||
default:
|
||||
- ramp:
|
||||
tag: linear_volume
|
||||
items:
|
||||
- string: {text: "🔈 "}
|
||||
- string: {text: "🔉 "}
|
||||
- string: {text: "🔊 "}
|
||||
type == "source":
|
||||
- string: {text: "🎙 "}
|
||||
- map:
|
||||
<<: *volume
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
67
doc/yambar-modules-pulse.5.scd
Normal file
67
doc/yambar-modules-pulse.5.scd
Normal file
|
@ -0,0 +1,67 @@
|
|||
yambar-modules-pulse(5)
|
||||
|
||||
# NAME
|
||||
pulse - Monitors a PulseAudio source and/or sink
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| online
|
||||
: bool
|
||||
: True when connected to the PulseAudio server
|
||||
| sink_online
|
||||
: bool
|
||||
: True when the sink is present
|
||||
| source_online
|
||||
: bool
|
||||
: True when the source is present
|
||||
| sink_percent
|
||||
: range
|
||||
: Sink volume level, as a percentage
|
||||
| source_percent
|
||||
: range
|
||||
: Source volume level, as a percentage
|
||||
| sink_muted
|
||||
: bool
|
||||
: True if the sink is muted, otherwise false
|
||||
| source_muted
|
||||
: bool
|
||||
: True if the source is muted, otherwise false
|
||||
| sink_port
|
||||
: string
|
||||
: Description of the active sink port
|
||||
| source_port
|
||||
: string
|
||||
: Description of the active source port
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| sink
|
||||
: string
|
||||
: no
|
||||
: Name of sink to monitor (default: _@DEFAULT\_SINK@_).
|
||||
| source
|
||||
: string
|
||||
: no
|
||||
: Name of source to monitor (default: _@DEFAULT\_SOURCE@_).
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- pulse:
|
||||
content:
|
||||
string: {text: "{sink_percent}% ({sink_port})"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
92
doc/yambar-modules-removables.5.scd
Normal file
92
doc/yambar-modules-removables.5.scd
Normal file
|
@ -0,0 +1,92 @@
|
|||
yambar-modules-removables(5)
|
||||
|
||||
# NAME
|
||||
removables - This module detects removable drives
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module detects removable drives (USB sticks, CD-ROMs) and
|
||||
instantiates the provided _content_ particle for each detected drive.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| vendor
|
||||
: string
|
||||
: Name of the drive vendor
|
||||
| model
|
||||
: string
|
||||
: Drive model name
|
||||
| optical
|
||||
: bool
|
||||
: True if the drive is an optical drive (CD-ROM, DVD-ROM etc)
|
||||
| audio
|
||||
: bool
|
||||
: True if an optical drive has an audio CD inserted (i.e. this
|
||||
property is always false for non-optical drives).
|
||||
| device
|
||||
: string
|
||||
: Volume device name (typically */dev/sd?*)
|
||||
| size
|
||||
: range
|
||||
: The volume's size, in bytes. The tag's maximum value is set to the
|
||||
underlying block device's size
|
||||
| label
|
||||
: string
|
||||
: The volume's label, or its size if it has no label
|
||||
| mounted
|
||||
: bool
|
||||
: True if the volume is mounted
|
||||
| mount_point
|
||||
: string
|
||||
: Path where the volume is mounted, or *""* if it is not mounted
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered volume
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered volume
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
| ignore
|
||||
: list of strings
|
||||
: no
|
||||
: List of device paths that should be ignored (e.g. /dev/mmcblk0, or /dev/mmcblk0p1)
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
right:
|
||||
- removables:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~mounted:
|
||||
string:
|
||||
on-click: udisksctl mount -b {device}
|
||||
text: "{label}"
|
||||
mounted:
|
||||
string:
|
||||
on-click: udisksctl unmount -b {device}
|
||||
text: "{label}"
|
||||
deco: {underline: {size: 2, color: ffffffff}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
109
doc/yambar-modules-river.5.scd
Normal file
109
doc/yambar-modules-river.5.scd
Normal file
|
@ -0,0 +1,109 @@
|
|||
yambar-modules-river(5)
|
||||
|
||||
# NAME
|
||||
river - This module provides information about the river tags
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module uses river's (https://github.com/ifreund/river, a dynamic
|
||||
tiling Wayland compositor) status protocol to provide information
|
||||
about the river tags.
|
||||
|
||||
It has an interface similar to the i3/sway module.
|
||||
|
||||
The configuration for the river module specifies one _title_ particle,
|
||||
which will be instantiated once for each seat, with tags representing
|
||||
the seats' name, the title of the seats' currently focused view, and
|
||||
its current river "mode".
|
||||
|
||||
It also specifies a _content_ template particle, which is instantiated
|
||||
once for all 32 river tags. This means you probably want to use a
|
||||
*map* particle to hide unused river tags.
|
||||
|
||||
# TAGS (for the "content" particle)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: River tag number
|
||||
| urgent
|
||||
: bool
|
||||
: True if the river tag has at least one urgent view.
|
||||
| visible
|
||||
: bool
|
||||
: True if the river tag is focused by at least one output (i.e. visible on at least one monitor).
|
||||
| focused
|
||||
: bool
|
||||
: True if the river tag is _visible_ and has keyboard focus.
|
||||
| occupied
|
||||
: bool
|
||||
: True if the river tag has views (i.e. windows).
|
||||
| state
|
||||
: string
|
||||
: Set to *urgent* if _urgent_ is true, *focused* if _focused_ is true,
|
||||
*unfocused* if _visible_ is true, but _focused_ is false, or
|
||||
*invisible* if the river tag is not visible on any monitors.
|
||||
|
||||
|
||||
# TAGS (for the "title" particle)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| seat
|
||||
: string
|
||||
: The name of the seat.
|
||||
| title
|
||||
: string
|
||||
: The seat's focused view's title.
|
||||
| mode
|
||||
: string
|
||||
: The seat's current mode (entered with e.g. *riverctl enter-mode foobar*).
|
||||
| layout
|
||||
: string
|
||||
: Current layout of the output currently focused by the seat.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| title
|
||||
: particle
|
||||
: no
|
||||
: Particle that will be instantiated with the _seat_ and _title_ tags.
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: Template particle that will be instantiated once for all of the 32 river tags.
|
||||
| all-monitors
|
||||
: bool
|
||||
: no
|
||||
: When set to false (the default), tags reflect river tags and seats
|
||||
for the monitor yambar is on only. When set to true, tags reflect
|
||||
the union of all monitors.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- river:
|
||||
title: {string: { text: "{seat} - {title} ({layout}/{mode})" }}
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~occupied: {empty: {}}
|
||||
occupied:
|
||||
string:
|
||||
margin: 5
|
||||
text: "{id}: {state}"
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
151
doc/yambar-modules-script.5.scd
Normal file
151
doc/yambar-modules-script.5.scd
Normal file
|
@ -0,0 +1,151 @@
|
|||
yambar-modules-script(5)
|
||||
|
||||
# NAME
|
||||
script - This module executes a user-provided script (or binary!)
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module executes a user-provided script (or binary!) that writes
|
||||
tags on its stdout.
|
||||
|
||||
Scripts can be run in two modes: yambar polled, or continuously. In the
|
||||
yambar polled mode, the script is expected to write one set of tags
|
||||
and then exit. Yambar will execute the script again after a
|
||||
configurable amount of time.
|
||||
|
||||
In continuous mode, the script is executed once. It will typically run
|
||||
in a loop, sending an updated tag set whenever it needs, or wants
|
||||
to. The last tag set is used (displayed) by yambar until a new tag set
|
||||
is received. This mode is intended to be used by scripts that depend
|
||||
on non-polling methods to update their state.
|
||||
|
||||
Tag sets, or _transactions_, are separated by an empty line
|
||||
(e.g. *echo ""*). The empty line is required to commit (update) the
|
||||
tag even for only one transaction.
|
||||
|
||||
Each _tag_ is a single line on the format:
|
||||
|
||||
```
|
||||
name|type|value
|
||||
```
|
||||
|
||||
Where _name_ is what you also use to refer to the tag in the yambar
|
||||
configuration, _type_ is one of the tag types defined in
|
||||
*yambar-tags*(5), and _value_ is the tag’s value.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
var1|string|hello
|
||||
var2|int|13
|
||||
<empty>
|
||||
var1|string|world
|
||||
var2|int|37
|
||||
<empty>
|
||||
```
|
||||
|
||||
The example above consists of two transactions. Each transaction has
|
||||
two tags: one string tag and one integer tag. The second transaction
|
||||
replaces the tags from the first transaction. Note that **both**
|
||||
transactions need to be terminated with an empty line.
|
||||
|
||||
Supported _types_ are:
|
||||
|
||||
- string
|
||||
- int
|
||||
- bool
|
||||
- float
|
||||
- range:n-m (e.g. *var|range:0-100|57*)
|
||||
|
||||
# TAGS
|
||||
|
||||
User defined.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| path
|
||||
: string
|
||||
: yes
|
||||
: Path to script/binary to execute. Must either be an absolute path,
|
||||
or start with *~/*.
|
||||
| args
|
||||
: list of strings
|
||||
: no
|
||||
: Arguments to pass to the script/binary.
|
||||
| poll-interval
|
||||
: integer
|
||||
: no
|
||||
: Number of milliseconds between each script run. If unset, or set to
|
||||
0, continuous mode is used.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
Here is an "hello world" example script:
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
echo "test|string|hello"
|
||||
echo ""
|
||||
sleep 3
|
||||
|
||||
echo "test|string|world"
|
||||
echo ""
|
||||
sleep 3
|
||||
done
|
||||
```
|
||||
|
||||
This script runs in continuous mode, and will emit a single string tag,
|
||||
_test_, and alternate its value between *hello* and *world* every
|
||||
three seconds.
|
||||
|
||||
A corresponding yambar configuration could look like this:
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- script:
|
||||
path: /path/to/script.sh
|
||||
args: []
|
||||
content: {string: {text: "{test}"}}
|
||||
```
|
||||
|
||||
Another example use case of this module could be to display currently playing
|
||||
song or other media from players that support MPRIS (Media Player Remote
|
||||
Interfacing Specification):
|
||||
|
||||
```
|
||||
bar:
|
||||
center:
|
||||
- script:
|
||||
path: /usr/bin/playerctl
|
||||
args:
|
||||
- "--follow"
|
||||
- "metadata"
|
||||
- "-f"
|
||||
- |
|
||||
status|string|{{status}}
|
||||
artist|string|{{artist}}
|
||||
title|string|{{title}}
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
status == Paused: {empty: {}}
|
||||
status == Playing:
|
||||
content: {string: {text: "{artist} - {title}"}}
|
||||
```
|
||||
|
||||
The above snippet runs a _playerctl_ utility in _--follow_ mode, reacting to
|
||||
media updates on DBUS and outputting status, artist and title of media being
|
||||
played in a format that is recognized by yambar. See _playerctl_ documentation
|
||||
for more available metadata fields and control over which players get used.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
70
doc/yambar-modules-sway-xkb.5.scd
Normal file
70
doc/yambar-modules-sway-xkb.5.scd
Normal file
|
@ -0,0 +1,70 @@
|
|||
yambar-modules-sway-xkb(5)
|
||||
|
||||
# NAME
|
||||
sway-xkb - This module monitor input devices' active XKB layout
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module uses *Sway* extensions to the I3 IPC API to monitor input
|
||||
devices' active XKB layout. As such, it requires Sway to be running.
|
||||
|
||||
*Note* that the _content_ configuration option is a *template*;
|
||||
*sway-xkb* will instantiate a particle list, where each item is
|
||||
instantiated from this template, and represents an input device.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: string
|
||||
: Input device identifier
|
||||
| layout
|
||||
: string
|
||||
: The input device's currently active XKB layout
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| identifiers
|
||||
: list of strings
|
||||
: yes
|
||||
: Identifiers of input devices to monitor. Use _swaymsg -t get_inputs_ to see available devices.
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: A particle template; each existing input device will be instantiated with this template.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered input device
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered input device
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- sway-xkb:
|
||||
identifiers:
|
||||
- 1523:7:HID_05f3:0007
|
||||
- 7247:2:USB_USB_Keykoard
|
||||
spacing: 5
|
||||
content: {string: {text: "{id}: {layout}"}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules-xkb*(5), *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
10
doc/yambar-modules-sway.5.scd
Normal file
10
doc/yambar-modules-sway.5.scd
Normal file
|
@ -0,0 +1,10 @@
|
|||
yambar-modules-sway(5)
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Please use the i3 (*yambar-modules-i3*(5)) module, as it is fully compatible with Sway
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-modules-i3*(5)
|
||||
|
52
doc/yambar-modules-xkb.5.scd
Normal file
52
doc/yambar-modules-xkb.5.scd
Normal file
|
@ -0,0 +1,52 @@
|
|||
yambar-modules-xkb(5)
|
||||
|
||||
# NAME
|
||||
xkb - This module monitors the currently active XKB keyboard layout
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module monitors the currently active XKB keyboard layout and
|
||||
lock-key states.
|
||||
|
||||
Note: this module is X11 only. It does not work in Wayland.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: Name of currently selected layout, long version (e.g. "English (US)")
|
||||
| symbol
|
||||
: string
|
||||
: Name of currently selected layout, short version (e.g. "us")
|
||||
| caps_lock
|
||||
: bool
|
||||
: True if *CapsLock* is enabled
|
||||
| num_lock
|
||||
: bool
|
||||
: True if *NumLock* is enabled
|
||||
| scroll_lock
|
||||
: bool
|
||||
: True if *ScrollLock* is enabled
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- xkb:
|
||||
content:
|
||||
string: {text: "{symbol}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules-sway-xkb*(5), *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
45
doc/yambar-modules-xwindow.5.scd
Normal file
45
doc/yambar-modules-xwindow.5.scd
Normal file
|
@ -0,0 +1,45 @@
|
|||
yambar-modules-xwindow(5)
|
||||
|
||||
# NAME
|
||||
xwindow - This module provides the application name and window title
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module provides the application name and window title of the
|
||||
currently focused window.
|
||||
|
||||
Note: this module is X11 only. It does not work in Wayland. If you are
|
||||
running Sway, take a look at the *i3* module and its _application_ and
|
||||
_title_ tags.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| application
|
||||
: string
|
||||
: Name of the application that owns the currently focused window
|
||||
| title
|
||||
: string
|
||||
: The title of the currently focused window
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- xwindow:
|
||||
content:
|
||||
string: {text: "{application}: {title}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
|
@ -38,7 +38,7 @@ For example, to render _backlight_ as " 20%", you could use:
|
|||
```
|
||||
content:
|
||||
- string:
|
||||
font: Font Awesome 5 Free:style=solid:pixelsize=14
|
||||
font: Font Awesome 6 Free:style=solid:pixelsize=14
|
||||
text:
|
||||
- string:
|
||||
font: Adobe Helvetica:pixelsize=12
|
||||
|
@ -68,20 +68,17 @@ in red.
|
|||
```
|
||||
content:
|
||||
map:
|
||||
tag: carrier
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~carrier: {empty: {}}
|
||||
carrier:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
up:
|
||||
conditions:
|
||||
state == up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
```
|
||||
|
||||
## Use yaml anchors
|
||||
|
@ -94,7 +91,7 @@ In these cases, you can define an anchor point, either at top-level,
|
|||
or in a module's _anchors_ attribute:
|
||||
|
||||
```
|
||||
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
|
||||
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14
|
||||
|
||||
```
|
||||
|
||||
|
@ -113,7 +110,7 @@ following attributes are supported by all modules:
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
|
@ -133,647 +130,59 @@ following attributes are supported by all modules:
|
|||
: Foreground (text) color of the content particle. This is an
|
||||
inherited attribute.
|
||||
|
||||
# ALSA
|
||||
# BUILT-IN MODULES
|
||||
|
||||
Monitors an alsa soundcard for volume and mute/unmute changes.
|
||||
Available modules have their own pages:
|
||||
|
||||
## TAGS
|
||||
*yambar-modules-alsa*(5)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| volume
|
||||
: range
|
||||
: Volume level, with min and max as start and end range values
|
||||
| muted
|
||||
: bool
|
||||
: True if muted, otherwise false
|
||||
*yambar-modules-backlight*(5)
|
||||
|
||||
*yambar-modules-battery*(5)
|
||||
|
||||
## CONFIGURATION
|
||||
*yambar-modules-clock*(5)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| card
|
||||
: string
|
||||
: yes
|
||||
: The soundcard name. _Default_ might work.
|
||||
| mixer
|
||||
: string
|
||||
: yes
|
||||
: Mixer channel to monitor. _Master_ might work.
|
||||
*yambar-modules-cpu*(5)
|
||||
|
||||
## EXAMPLES
|
||||
*yambar-modules-disk-io*(5)
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- alsa:
|
||||
card: hw:PCH
|
||||
mixer: Master
|
||||
content: {string: {text: "{volume}"}}
|
||||
```
|
||||
*yambar-modules-dwl*(5)
|
||||
|
||||
# BACKLIGHT
|
||||
*yambar-modules-foreign-toplevel*(5)
|
||||
|
||||
This module reads monitor backlight status from
|
||||
_/sys/class/backlight_, and uses *udev* to monitor for changes.
|
||||
*yambar-modules-i3*(5)
|
||||
|
||||
## TAGS
|
||||
*yambar-modules-label*(5)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| brightness
|
||||
: range
|
||||
: The current brightness level, in absolute value
|
||||
| percent
|
||||
: range
|
||||
: The current brightness level, in percent
|
||||
*yambar-modules-mem*(5)
|
||||
|
||||
## CONFIGURATION
|
||||
*yambar-modules-mpd*(5)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: yes
|
||||
: The backlight device's name (one of the names in */sys/class/backlight*)
|
||||
*yambar-modules-network*(5)
|
||||
|
||||
## EXAMPLES
|
||||
*yambar-modules-pipewire*(5)
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- backlight:
|
||||
name: intel_backlight
|
||||
content:
|
||||
string: {text: "backlight: {percent}%"}
|
||||
```
|
||||
*yambar-modules-pulse*(5)
|
||||
|
||||
# BATTERY
|
||||
*yambar-modules-removables*(5)
|
||||
|
||||
This module reads battery status from _/sys/class/power_supply_ and
|
||||
uses *udev* to monitor for changes.
|
||||
*yambar-modules-river*(5)
|
||||
|
||||
## TAGS
|
||||
*yambar-modules-script*(5)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: Battery device name
|
||||
| manufacturer
|
||||
: string
|
||||
: Name of the battery manufacturer
|
||||
| model
|
||||
: string
|
||||
: Battery model name
|
||||
| state
|
||||
: string
|
||||
: One of *full*, *charging*, *discharging* or *unknown*
|
||||
| capacity
|
||||
: range
|
||||
: capacity left, in percent
|
||||
| estimate
|
||||
: string
|
||||
: Estimated time left (to empty while discharging, or to full while
|
||||
charging), formatted as HH:MM.
|
||||
*yambar-modules-sway-xkb*(5)
|
||||
|
||||
## CONFIGURATION
|
||||
*yambar-modules-sway*(5)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: yes
|
||||
: Battery device name (one of the names in */sys/class/power_supply*)
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: How often, in seconds, to poll for capacity changes (default=*60*)
|
||||
*yambar-modules-niri-language*(5)
|
||||
|
||||
## EXAMPLES
|
||||
*yambar-modules-niri-workspaces*(5)
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30
|
||||
content:
|
||||
string: {text: "BAT: {capacity}% {estimate}"}
|
||||
```
|
||||
*yambar-modules-xkb*(5)
|
||||
|
||||
# CLOCK
|
||||
|
||||
This module provides the current date and time.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| time
|
||||
: string
|
||||
: Current time, formatted using the _time-format_ attribute
|
||||
| date
|
||||
: string
|
||||
: Current date, formatted using the _date-format_ attribute
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| time-format
|
||||
: string
|
||||
: no
|
||||
: *strftime* formatter for the _time_ tag (default=*%H:%M*)
|
||||
| date-format
|
||||
: string
|
||||
: no
|
||||
: *strftime* formatter for the _date_ date (default=*%x*)
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- clock:
|
||||
time-format: "%H:%M %Z"
|
||||
content:
|
||||
string: {text: "{date} {time}"}
|
||||
```
|
||||
|
||||
# I3 (and Sway)
|
||||
|
||||
This module monitors i3 and sway workspaces.
|
||||
|
||||
Unlike other modules where the _content_ attribute is just a single
|
||||
*particle*, the i3 module's _content_ is an associative array mapping
|
||||
i3/sway workspace names to a particle.
|
||||
|
||||
You can add an empty workspace name, *""*, as a catch-all workspace
|
||||
particle. The *i3* module will fallback to this entry if it cannot
|
||||
find the workspace name in the _content_ map.
|
||||
|
||||
It also recognizes the special name *current*, which always represents
|
||||
the currently focused workspace. On Sway, this can be used together
|
||||
with the _application_ and _title_ tags to replace the X11-only
|
||||
*xwindow* module.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: The workspace name
|
||||
| visible
|
||||
: bool
|
||||
: True if the workspace is currently visible (on any output)
|
||||
| focused
|
||||
: bool
|
||||
: True if the workspace is currently focused
|
||||
| urgent
|
||||
: bool
|
||||
: True if the workspace has the urgent flag set
|
||||
| state
|
||||
: string
|
||||
: One of *urgent*, *focused*, *unfocused* or *invisible* (note:
|
||||
*unfocused* is when it is visible, but neither focused nor urgent).
|
||||
| application
|
||||
: string
|
||||
: Name of application currently focused on this workspace (Sway only - use the *xwindow* module in i3)
|
||||
| title
|
||||
: string
|
||||
: This workspace's focused window's title
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| content
|
||||
: associative array
|
||||
: yes
|
||||
: Unlike other modules, _content_ is an associative array mapping
|
||||
workspace names to particles. Use *""* to specify a default
|
||||
fallback particle, or *current* for the currently active workspace.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the left-side of each rendered workspace particle
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right-side of each rendered workspace particle
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
This renders all workspace names, with an *\** indicating the
|
||||
currently focused one. It also renders the currently focused
|
||||
application name and window title.
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- i3:
|
||||
content:
|
||||
"":
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: "{name}"}}
|
||||
values:
|
||||
focused: {string: {text: "{name}*"}}
|
||||
current: { string: {text: "{application}: {title}"}}
|
||||
```
|
||||
|
||||
# LABEL
|
||||
|
||||
This module renders the provided _content_ particle, but provides no
|
||||
additional data.
|
||||
|
||||
## TAGS
|
||||
|
||||
None
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION*)
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- label:
|
||||
content: {string: {text: hello world}}
|
||||
```
|
||||
|
||||
# MPD
|
||||
|
||||
This module provides MPD status such as currently playing
|
||||
artist/album/song.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| state
|
||||
: string
|
||||
: One of *offline*, *stopped*, *paused* or *playing*
|
||||
| repeat
|
||||
: bool
|
||||
: True if the *repeat* flag is set
|
||||
| random
|
||||
: bool
|
||||
: True if the *random* flag is set
|
||||
| consume
|
||||
: bool
|
||||
: True if the *consume* flag is set
|
||||
| album
|
||||
: string
|
||||
: Currently playing album (also valid in *paused* state)
|
||||
| artist
|
||||
: string
|
||||
: Artist of currently playing song (also valid in *paused* state)
|
||||
| title
|
||||
: string
|
||||
: Title of currently playing song (also valid in *paused* state)
|
||||
| pos
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's current position
|
||||
(also see _elapsed_)
|
||||
| end
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's total length (also
|
||||
see _duration_)
|
||||
| elapsed
|
||||
: realtime
|
||||
: Position in currently playing song, in milliseconds. Can be used
|
||||
with a _progress-bar_ particle.
|
||||
| duration
|
||||
: int
|
||||
: Length of currently playing song, in milliseconds
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| host
|
||||
: string
|
||||
: yes
|
||||
: Hostname/IP/unix-socket to connect to
|
||||
| port
|
||||
: int
|
||||
: no
|
||||
: TCP port to connect to
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- mpd:
|
||||
host: /run/mpd/socket
|
||||
content:
|
||||
string: {text: "{artist} - {album} - {title} ({end})"}
|
||||
```
|
||||
|
||||
# NETWORK
|
||||
|
||||
This module monitors network connection state; disconnected/connected
|
||||
state and MAC/IP addresses.
|
||||
|
||||
Note: while the module internally tracks all assigned IPv4/IPv6
|
||||
addresses, it currently exposes only a single IPv4 and a single IPv6
|
||||
address.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: Network interface name
|
||||
| index
|
||||
: int
|
||||
: Network interface index
|
||||
| carrier
|
||||
: bool
|
||||
: True if the interface has CARRIER. That is, if it is physically connected.
|
||||
| state
|
||||
: string
|
||||
: One of *unknown*, *not present*, *down*, *lower layers down*,
|
||||
*testing*, *dormant* or *up*. You are probably interrested in *down* and *up*.
|
||||
| mac
|
||||
: string
|
||||
: MAC address
|
||||
| ipv4
|
||||
: string
|
||||
: IPv4 address assigned to the interface, or *""* if none
|
||||
| ipv6
|
||||
: string
|
||||
: IPv6 address assigned to the interface, or *""* if none
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: Name of network interface to monitor
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- network:
|
||||
name: wlp3s0
|
||||
content:
|
||||
string: {text: "{name}: {state} ({ipv4})"}
|
||||
```
|
||||
|
||||
# REMOVABLES
|
||||
|
||||
This module detects removable drives (USB sticks, CD-ROMs) and
|
||||
instantiates the provided _content_ particle for each detected drive.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| vendor
|
||||
: string
|
||||
: Name of the drive vendor
|
||||
| model
|
||||
: string
|
||||
: Drive model name
|
||||
| optical
|
||||
: bool
|
||||
: True if the drive is an optical drive (CD-ROM, DVD-ROM etc)
|
||||
| device
|
||||
: string
|
||||
: Volume device name (typically */dev/sd?*)
|
||||
| size
|
||||
: range
|
||||
: The volume's size, in bytes. The tag's maximum value is set to the
|
||||
underlying block device's size
|
||||
| label
|
||||
: string
|
||||
: The volume's label, or its size if it has no label
|
||||
| mounted
|
||||
: bool
|
||||
: True if the volume is mounted
|
||||
| mount_point
|
||||
: string
|
||||
: Path where the volume is mounted, or *""* if it is not mounted
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered volume
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered volume
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
right:
|
||||
- removables:
|
||||
content:
|
||||
map:
|
||||
tag: mounted
|
||||
values:
|
||||
false:
|
||||
string:
|
||||
on-click: udisksctl mount -b {device}
|
||||
text: "{label}"
|
||||
true:
|
||||
string:
|
||||
on-click: udisksctl unmount -b {device}
|
||||
text: "{label}"
|
||||
deco: {underline: {size: 2, color: ffffffff}}
|
||||
```
|
||||
|
||||
# SWAY-XKB
|
||||
|
||||
This module uses *Sway* extenions to the I3 IPC API to monitor input
|
||||
devices' active XKB layout. As such, it requires Sway to be running.
|
||||
|
||||
*Note* that the _content_ configuration option is a *template*;
|
||||
*sway-xkb* will instantiate a particle list, where each item is
|
||||
instantiated from this template, and represents an input device.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| id
|
||||
: string
|
||||
: Input device indentifier
|
||||
| layout
|
||||
: string
|
||||
: The input device's currently active XKB layout
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| identifiers
|
||||
: list of strings
|
||||
: yes
|
||||
: Identifiers of input devices to monitor. Use _swaymsg -t get_inputs_ to see available devices.
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: A particle template; each existing input device will be instantiated with this template.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered input device
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered input device
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- sway-xkb:
|
||||
identifiers:
|
||||
- 1523:7:HID_05f3:0007
|
||||
- 7247:2:USB_USB_Keykoard
|
||||
spacing: 5
|
||||
content: {string: {text: "{id}: {layout}"}}
|
||||
```
|
||||
|
||||
# XKB
|
||||
|
||||
This module monitors the currently active XKB keyboard layout and
|
||||
lock-key states.
|
||||
|
||||
Note: this module is X11 only. It does not work in Wayland.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: Name of currently selected layout, long version (e.g. "English (US)")
|
||||
| symbol
|
||||
: string
|
||||
: Name of currently selected layout, short version (e.g. "us")
|
||||
| caps_lock
|
||||
: bool
|
||||
: True if *CapsLock* is enabled
|
||||
| num_lock
|
||||
: bool
|
||||
: True if *NumLock* is enabled
|
||||
| scroll_lock
|
||||
: bool
|
||||
: True if *ScrollLock* is enabled
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION*)
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- xkb:
|
||||
content:
|
||||
string: {text: "{symbol}"}
|
||||
```
|
||||
|
||||
# XWINDOW
|
||||
|
||||
This module provides the application name and window title of the
|
||||
currently focused window.
|
||||
|
||||
Note: this module is X11 only. It does not work in Wayland. If you are
|
||||
running Sway, take a look at the *i3* module and its _application_ and
|
||||
_title_ tags.
|
||||
|
||||
## TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| application
|
||||
: string
|
||||
: Name of the application that owns the currently focused window
|
||||
| title
|
||||
: string
|
||||
: The title of the currently focused window
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION*)
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- xwindow:
|
||||
content:
|
||||
string: {text: "{application}: {title}"}
|
||||
```
|
||||
*yambar-modules-xwindow*(5)
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ following attributes are supported by all particles:
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| left-margin
|
||||
: int
|
||||
: no
|
||||
|
@ -31,20 +31,76 @@ following attributes are supported by all particles:
|
|||
: Font to use. Note that this is an inherited attribute; i.e. you can
|
||||
set it on e.g. a _list_ particle, and it will apply to all
|
||||
particles in the list.
|
||||
| font-shaping
|
||||
: enum
|
||||
: no
|
||||
: font-shaping; one of _full_ or _none_. When set to _full_ (the
|
||||
default), strings will be "shaped" using HarfBuzz. Requires support
|
||||
in fcft.
|
||||
| foreground
|
||||
: color
|
||||
: no
|
||||
: Foreground (text) color. Just like _font_, this is an inherited attribute.
|
||||
| on-click
|
||||
: associative array/string
|
||||
: no
|
||||
: When set to a string, executes the string as a command when the
|
||||
particle is left-clicked. Tags can be used. Note that the string is
|
||||
*not* executed in a shell. Environment variables are not expanded.
|
||||
*~/* is expanded, but only in the first argument. The same applies
|
||||
to all attributes associated with it, below.
|
||||
| on-click.left
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked. Tags can be
|
||||
used. Note that the string is *not* executed in a shell.
|
||||
: Command to execute when the particle is left-clicked.
|
||||
| on-click.right
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is right-clicked.
|
||||
| on-click.middle
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is middle-clicked.
|
||||
| on-click.wheel-up
|
||||
: string
|
||||
: no
|
||||
: Command to execute every time a 'wheel-up' event is triggered.
|
||||
| on-click.wheel-down
|
||||
: string
|
||||
: no
|
||||
: Command to execute every time a 'wheel-down' event is triggered.
|
||||
| on-click.previous
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked with the 'previous' button.
|
||||
| on-click.next
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked with the 'next' button.
|
||||
| deco
|
||||
: decoration
|
||||
: no
|
||||
: Decoration to apply to the particle. See *yambar-decorations*(5)
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
*on-click* as a string (handles left click):
|
||||
```
|
||||
content:
|
||||
<particle>:
|
||||
on-click: command args
|
||||
```
|
||||
|
||||
*on-click* as an associative array (handles other buttons):
|
||||
```
|
||||
content:
|
||||
<particle>:
|
||||
on-click:
|
||||
left: command-1
|
||||
wheel-up: command-3
|
||||
wheel-down: command-4
|
||||
```
|
||||
|
||||
# STRING
|
||||
|
||||
This is the most basic particle. It takes a format string, consisting
|
||||
|
@ -59,7 +115,7 @@ of free text mixed with tag specifiers.
|
|||
| text
|
||||
: string
|
||||
: yes
|
||||
: Format string. Tags are spcified with _{tag_name}_. Some tag types
|
||||
: Format string. Tags are specified with _{tag_name}_. Some tag types
|
||||
have suffixes that can be appended (e.g. _{tag_name:suffix}_). See
|
||||
*yambar-modules*(5)).
|
||||
| max
|
||||
|
@ -67,9 +123,9 @@ of free text mixed with tag specifiers.
|
|||
: no
|
||||
: Sets the rendered string's maximum length. If the final string's
|
||||
length exceeds this, the rendered string will be truncated, and
|
||||
"..." will be appended. Note that the trailing "..." are
|
||||
"…" will be appended. Note that the trailing "…" is
|
||||
*included* in the maximum length. I.e. if you set _max_ to '5', you
|
||||
will only get *2* characters from the string.
|
||||
will only get *4* characters from the string.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
@ -82,7 +138,7 @@ content:
|
|||
# EMPTY
|
||||
|
||||
This particle is a place-holder. While it does not render any tags,
|
||||
margins and decortions are rendered.
|
||||
margins and decorations are rendered.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
|
@ -99,7 +155,7 @@ content:
|
|||
|
||||
This particle is a list (or sequence, if you like) of other
|
||||
particles. It can be used to render e.g. _string_ particles with
|
||||
different font and/or color formatting. Or ay other particle
|
||||
different font and/or color formatting. Or any other particle
|
||||
combinations.
|
||||
|
||||
But note that this means you *cannot* set any attributes on the _list_
|
||||
|
@ -110,7 +166,7 @@ particle itself.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| items
|
||||
: list
|
||||
: yes
|
||||
|
@ -158,51 +214,165 @@ content:
|
|||
- string: ...
|
||||
```
|
||||
|
||||
Note that the short form has a hard-coded *right-spacing* of 2. This
|
||||
cannot be changed. If you want a different spacing, you must use an
|
||||
explicit list particle (i.e. the long form).
|
||||
|
||||
|
||||
# MAP
|
||||
|
||||
This particle maps the values of a specific tag to different
|
||||
particles. In addition to explicit tag values, you can also specify a
|
||||
particles based on conditions. A condition takes either the form of:
|
||||
|
||||
```
|
||||
<tag> <operation> <value>
|
||||
```
|
||||
|
||||
Or, for boolean tags:
|
||||
|
||||
```
|
||||
<tag>
|
||||
```
|
||||
|
||||
Where <tag> is the tag you would like to map, <operation> is one of:
|
||||
|
||||
[- ==
|
||||
:- !=
|
||||
:- >=
|
||||
:- >
|
||||
:- <=
|
||||
:- <
|
||||
|
||||
and <value> is the value you would like to compare it to. *If the
|
||||
value contains any non-alphanumerical characters, you must
|
||||
surround it with ' \" ' *:
|
||||
|
||||
```
|
||||
"hello world"
|
||||
"@#$%"
|
||||
```
|
||||
|
||||
Negation is done with a preceding '~':
|
||||
|
||||
```
|
||||
~<tag>
|
||||
~<condition>
|
||||
```
|
||||
|
||||
To match for empty strings, use ' "" ':
|
||||
|
||||
```
|
||||
<tag> == ""
|
||||
```
|
||||
|
||||
String glob matching
|
||||
|
||||
To perform string matching using globbing with "\*" & "?" characters:
|
||||
\* Match any zero or more characters. ? Match exactly any one
|
||||
character.
|
||||
|
||||
```
|
||||
<tag> ~~ "hello*"
|
||||
```
|
||||
|
||||
Will match any string starting with "hello", including "hello",
|
||||
"hello1", "hello123", etc.
|
||||
|
||||
```
|
||||
<tag> ~~ "hello?"
|
||||
```
|
||||
|
||||
Will match any string starting with "hello" followed by any single
|
||||
character, including "hello1", "hello-", but not "hello".
|
||||
|
||||
Furthermore, you may use the boolean operators:
|
||||
|
||||
[- &&
|
||||
:- ||
|
||||
|
||||
in order to create more complex conditions:
|
||||
|
||||
```
|
||||
<condition1> && <condition2>
|
||||
```
|
||||
|
||||
You may surround <condition> with parenthesis for clarity or
|
||||
specifying precedence:
|
||||
|
||||
```
|
||||
(<condition>)
|
||||
<condition1> && (<condition2> || <condition3>)
|
||||
```
|
||||
|
||||
In addition to explicit tag values, you can also specify a
|
||||
default/fallback particle.
|
||||
|
||||
Note that conditions are evaluated in the order they appear. *If
|
||||
multiple conditions are true, the first one will be used*. This means
|
||||
that in a configuration such as:
|
||||
|
||||
```
|
||||
tx-bitrate > 1000:
|
||||
tx-bitrate > 1000000:
|
||||
```
|
||||
|
||||
the second condition would never run, since whenever the second
|
||||
condition is true, the first is also true. The correct way of doing
|
||||
this would be to invert the order of the conditions:
|
||||
|
||||
```
|
||||
tx-bitrate > 1000000:
|
||||
tx-bitrate > 1000:
|
||||
```
|
||||
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
: The tag (name of) which values should be mapped
|
||||
| values
|
||||
:< *Description*
|
||||
| conditions
|
||||
: associative array
|
||||
: yes
|
||||
: An associative array of tag values mapped to particles
|
||||
: An associative array of conditions (see above) mapped to particles
|
||||
| default
|
||||
: particle
|
||||
: no
|
||||
: Default particle to use, when tag's value does not match any of the
|
||||
mapped values.
|
||||
: Default particle to use, none of the conditions are true
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
map:
|
||||
tag: tag_name
|
||||
default:
|
||||
string:
|
||||
text: this is the default particle; the tag's value is now {tag_name}
|
||||
values:
|
||||
one_value:
|
||||
conditions:
|
||||
tag == one_value:
|
||||
string:
|
||||
text: tag's value is now one_value
|
||||
another_value:
|
||||
tag == another_value:
|
||||
string:
|
||||
text: tag's value is now another_value
|
||||
|
||||
```
|
||||
|
||||
For a boolean tag:
|
||||
|
||||
```
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
tag:
|
||||
string:
|
||||
text: tag is true
|
||||
~tag:
|
||||
string:
|
||||
text: tag is false
|
||||
```
|
||||
|
||||
# RAMP
|
||||
|
||||
This particle uses a range tag to index into an array of
|
||||
|
@ -215,7 +385,7 @@ indicator.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
|
@ -224,8 +394,20 @@ indicator.
|
|||
: list
|
||||
: yes
|
||||
: List of particles. Note that the tag value is *not* used as-is; its
|
||||
minumum and maximum values are used to map the tag's range to the
|
||||
minimum and maximum values are used to map the tag's range to the
|
||||
particle list's range.
|
||||
| min
|
||||
: int
|
||||
: no
|
||||
: If present this will be used as a lower bound instead of the tags
|
||||
minimum value. Tag values falling outside the defined range will
|
||||
get clamped to min/max.
|
||||
| max
|
||||
: int
|
||||
: no
|
||||
: If present this will be used as an upper bound instead of the tags
|
||||
maximum value. Tag values falling outside the defined range will
|
||||
get clamped to min/max.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
@ -258,7 +440,7 @@ itself when needed.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
|
@ -294,7 +476,7 @@ itself when needed.
|
|||
|
||||
```
|
||||
content:
|
||||
progres-bar:
|
||||
progress-bar:
|
||||
tag: tag_name
|
||||
length: 20
|
||||
start: {string: {text: ├}}
|
||||
|
|
|
@ -11,7 +11,7 @@ their information. Each module defines its own set of tags.
|
|||
The available tag *types* are:
|
||||
|
||||
[[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| string
|
||||
: Value is a string. Rendered as-is by the _string_ particle.
|
||||
| int
|
||||
|
@ -25,14 +25,110 @@ The available tag *types* are:
|
|||
| range
|
||||
: Value is an integer, with a minimum and maximum value associated
|
||||
with it. By default, the _string_ particle renders the value. The
|
||||
_:min_ or _:max_ suffixes by be added to instead render the mininum
|
||||
or maximum value (_\"{tag_name:min}\"_).
|
||||
*:min* or *:max* suffixes may be added to instead render the
|
||||
minimum or maximum value (_\"{tag_name:min}\"_).
|
||||
| realtime
|
||||
: Value is an integer that changes in a predictable manner (in
|
||||
"realtime"). This allows the particle to update itself
|
||||
periodically. Only supported by the
|
||||
*yambar-particle-progress-bar*(5). Other particles can still render
|
||||
the tag's value. And, the _string_ particle recognizes the _:unit_
|
||||
the tag's value. And, the _string_ particle recognizes the *:unit*
|
||||
suffix, which will be translated to a "s" for a tag with "seconds"
|
||||
resolution, or "ms" for one with "milliseconds" resolution.
|
||||
|
||||
# FORMATTING
|
||||
|
||||
A tag may be followed by one or more formatters that alter the tags
|
||||
rendition.
|
||||
|
||||
Formatters are added by appending a ':' separated list of formatter
|
||||
names:
|
||||
|
||||
"{tag_name:max:hex}"
|
||||
|
||||
In the table below, "kind" describes the type of action performed by
|
||||
the formatter:
|
||||
|
||||
- *format*: changes the representation of the tag's value
|
||||
- *selector*: changes what to render
|
||||
|
||||
In general, formatters of the same kind cannot be combined; if
|
||||
multiple formatters of the same kind are specified, the last one will
|
||||
be used.
|
||||
|
||||
[[ *Formatter*
|
||||
:[ *Kind*
|
||||
:[ *Applies to*
|
||||
:< *Description*
|
||||
| [0]<number>[.]
|
||||
: format
|
||||
: Numeric tags (integer and floats)
|
||||
: The width reserved to the field. The leading '0' is optional and
|
||||
indicates zero padding, as opposed to space padding. The trailing
|
||||
'.' is also optional
|
||||
| .<number>
|
||||
: format
|
||||
: Float tags
|
||||
: How many decimals to print
|
||||
| [0]<N>[.]<M>
|
||||
: format
|
||||
: N: numeric tags, M: float tags
|
||||
: Combined version of the two previous formatters
|
||||
| hex
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value in hex
|
||||
| oct
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value in octal
|
||||
| %
|
||||
: format
|
||||
: Range tags
|
||||
: Renders a range tag's value as a percentage value
|
||||
| /N
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value (in decimal) divided by N
|
||||
| kb, mb, gb
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value (in decimal) divided by 1000, 1000^2 or
|
||||
1000^3. Note: no unit suffix is appended
|
||||
| kib, mib, gib
|
||||
: format
|
||||
: All tag types
|
||||
: Same as *kb*, *mb* and *gb*, but divide by 1024^n instead of 1000^n.
|
||||
| min
|
||||
: selector
|
||||
: Range tags
|
||||
: Renders a range tag's minimum value
|
||||
| max
|
||||
: selector
|
||||
: Range tags
|
||||
: Renders a range tag's maximum value
|
||||
| unit
|
||||
: selector
|
||||
: Realtime tags
|
||||
: Renders a realtime tag's unit (e.g. "s", or "ms")
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
- A numeric (float or int) tag with at least 3 digits, zero-padded if
|
||||
necessary:
|
||||
|
||||
```
|
||||
{tag:03}
|
||||
```
|
||||
|
||||
- A float tag with 2 decimals:
|
||||
|
||||
```
|
||||
{tag:.2}
|
||||
```
|
||||
|
||||
- A "byte count" tag in gigabytes:
|
||||
|
||||
```
|
||||
{tag:gib}GB
|
||||
```
|
||||
|
|
|
@ -25,7 +25,11 @@ yambar - modular status panel for X11 and Wayland
|
|||
*-p*,*--print-pid*=_FILE_|_FD_
|
||||
Print PID to this file, or FD, when successfully started. The file
|
||||
(or FD) is closed immediately after writing the PID. When a _FILE_
|
||||
as been specified, the file is unlinked when yambar exits.
|
||||
as been specified, the file is unlinked upon exiting.
|
||||
|
||||
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
|
||||
Log level, used both for log output on stderr as well as
|
||||
syslog. Default: _warning_.
|
||||
|
||||
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
|
||||
Enables or disables colorization of log output on stderr.
|
||||
|
|
|
@ -12,9 +12,10 @@ and reference them using anchors.
|
|||
Besides the normal yaml types, there are a couple of yambar specific
|
||||
types that are frequently used:
|
||||
|
||||
- *font*: this is a string in _fontconfig_ format. Example of valid values:
|
||||
- Font Awesome 5 Brands
|
||||
- Font Awesome 5 Free:style=solid
|
||||
- *font*: this is a comma separated list of fonts in _fontconfig_
|
||||
format. Example of valid values:
|
||||
- Font Awesome 6 Brands
|
||||
- Font Awesome 6 Free:style=solid
|
||||
- Dina:pixelsize=10:slant=italic
|
||||
- Dina:pixelsize=10:weight=bold
|
||||
- *color*: an rgba hexstring; _RRGGBBAA_. Examples:
|
||||
|
@ -22,12 +23,17 @@ types that are frequently used:
|
|||
- 000000ff: black, no transparency
|
||||
- 00ff00ff: green, no transparency
|
||||
- ff000099: red, semi-transparent
|
||||
- *environment reference*: a string that contains format ${VAR}. This will be
|
||||
replaced by the value of the environment variable VAR. Example:
|
||||
- ${HOME}
|
||||
- ${HOME}/.config/yambar
|
||||
- ENV is ${ENV}, ENV2 is ${ENV2}
|
||||
|
||||
# FORMAT
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| height
|
||||
: int
|
||||
: yes
|
||||
|
@ -45,6 +51,11 @@ types that are frequently used:
|
|||
: no
|
||||
: Monitor to place the bar on. If not specified, the primary monitor will be
|
||||
used
|
||||
| layer
|
||||
: string
|
||||
: no
|
||||
: Layer to put bar on. One of _overlay_, _top_, _bottom_ or
|
||||
_background_. Wayland only. Default: _bottom_.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
|
@ -73,10 +84,26 @@ types that are frequently used:
|
|||
: associative array
|
||||
: no
|
||||
: Configures the border around the status bar
|
||||
| border.left-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the left side, in pixels
|
||||
| border.right-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the right side, in pixels
|
||||
| border.top-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the top side, in pixels
|
||||
| border.bottom-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the bottom side, in pixels
|
||||
| border.width
|
||||
: int
|
||||
: no
|
||||
: Width, in pixels, of the border
|
||||
: Short-hand for setting _border.left/right/top/bottom-width_
|
||||
| border.color
|
||||
: color
|
||||
: no
|
||||
|
@ -104,11 +131,27 @@ types that are frequently used:
|
|||
| font
|
||||
: font
|
||||
: no
|
||||
: Default font to use in modules and particles
|
||||
: Default font to use in modules and particles. May also be a comma
|
||||
separated list of several fonts, in which case the first font is
|
||||
the primary font, and the rest fallback fonts. These are yambar
|
||||
custom fallback fonts that will be searched before the fontconfig
|
||||
provided fallback list.
|
||||
| font-shaping
|
||||
: enum
|
||||
: no
|
||||
: Default setting for font-shaping, for use in particles. One of
|
||||
_full_ or _none_. When set to _full_ (the default), strings will be
|
||||
"shaped" using HarfBuzz. Requires support in fcft.
|
||||
| foreground
|
||||
: color
|
||||
: no
|
||||
: Default foreground (text) color to use
|
||||
| trackpad-sensitivity
|
||||
: int
|
||||
: no
|
||||
: How easy it is to trigger wheel-up and wheel-down on-click
|
||||
handlers. Higher values means you need to drag your finger a longer
|
||||
distance. The default is 30.
|
||||
| left
|
||||
: list
|
||||
: no
|
||||
|
@ -135,8 +178,8 @@ bar:
|
|||
|
||||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: "{time}"}
|
||||
content:
|
||||
- string: {text: "{time}"}
|
||||
```
|
||||
|
||||
# FILES
|
||||
|
|
289
examples/configurations/laptop.conf
Normal file
289
examples/configurations/laptop.conf
Normal file
|
@ -0,0 +1,289 @@
|
|||
# Typical laptop setup, with wifi, brightness, battery etc, for
|
||||
# i3/Sway.
|
||||
|
||||
# For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and
|
||||
# the sway-xkb module with the xkb module.
|
||||
|
||||
# fonts we'll be reusing here and there
|
||||
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14
|
||||
awesome_brands: &awesome_brands Font Awesome 6 Brands:pixelsize=16
|
||||
|
||||
std_underline: &std_underline {underline: { size: 2, color: ff0000ff}}
|
||||
|
||||
# This is THE bar configuration
|
||||
bar:
|
||||
height: 26
|
||||
location: top
|
||||
spacing: 5
|
||||
margin: 7
|
||||
|
||||
# Default font
|
||||
font: Adobe Helvetica:pixelsize=12
|
||||
|
||||
foreground: ffffffff
|
||||
background: 111111cc
|
||||
|
||||
border:
|
||||
width: 1
|
||||
color: 999999cc
|
||||
margin: 5
|
||||
top-margin: 0
|
||||
|
||||
left:
|
||||
- i3:
|
||||
anchors: # Not used (directly) by f00bar; used here to avoid duplication
|
||||
- string: &i3_common {margin: 5, on-click: "swaymsg --quiet workspace {name}"}
|
||||
- string: &default {<<: *i3_common, text: "? {name}"}
|
||||
- string: &main {<<: *i3_common, text: , font: *awesome}
|
||||
- string: &surfing {<<: *i3_common, text: , font: *awesome_brands}
|
||||
- string: &misc {<<: *i3_common, text: , font: *awesome}
|
||||
- string: &mail {<<: *i3_common, text: , font: *awesome}
|
||||
- string: &music {<<: *i3_common, text: , font: *awesome}
|
||||
- focused: &focused
|
||||
deco: {stack: [background: {color: ffa0a04c}, <<: *std_underline]}
|
||||
- invisible: &invisible {foreground: ffffff55}
|
||||
- urgent: &urgent
|
||||
foreground: 000000ff
|
||||
deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]}
|
||||
- map: &i3_mode
|
||||
default:
|
||||
- string:
|
||||
margin: 5
|
||||
text: "{mode}"
|
||||
deco: {background: {color: cc421dff}}
|
||||
- empty: {right-margin: 7}
|
||||
conditions:
|
||||
mode == default: {empty: {}}
|
||||
content:
|
||||
"":
|
||||
map:
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*default, *focused]}}
|
||||
state == unfocused: {string: {<<: *default}}
|
||||
state == invisible: {string: {<<: [*default, *invisible]}}
|
||||
state == urgent: {string: {<<: [*default, *urgent]}}
|
||||
main:
|
||||
map:
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*main, *focused]}}
|
||||
state == unfocused: {string: {<<: *main}}
|
||||
state == invisible: {string: {<<: [*main, *invisible]}}
|
||||
state == urgent: {string: {<<: [*main, *urgent]}}
|
||||
surfing:
|
||||
map:
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*surfing, *focused]}}
|
||||
state == unfocused: {string: {<<: *surfing}}
|
||||
state == invisible: {string: {<<: [*surfing, *invisible]}}
|
||||
state == urgent: {string: {<<: [*surfing, *urgent]}}
|
||||
misc:
|
||||
map:
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*misc, *focused]}}
|
||||
state == unfocused: {string: {<<: *misc}}
|
||||
state == invisible: {string: {<<: [*misc, *invisible]}}
|
||||
state == urgent: {string: {<<: [*misc, *urgent]}}
|
||||
|
||||
mail:
|
||||
map:
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*mail, *focused]}}
|
||||
state == unfocused: {string: {<<: *mail}}
|
||||
state == invisible: {string: {<<: [*mail, *invisible]}}
|
||||
state == urgent: {string: {<<: [*mail, *urgent]}}
|
||||
music:
|
||||
map:
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*music, *focused]}}
|
||||
state == unfocused: {string: {<<: *music}}
|
||||
state == invisible: {string: {<<: [*music, *invisible]}}
|
||||
state == urgent: {string: {<<: [*music, *urgent]}}
|
||||
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: "{app-id}", foreground: ffa0a0ff}
|
||||
- string: {text: ": {title}"}
|
||||
center:
|
||||
- mpd:
|
||||
host: /run/mpd/socket
|
||||
anchors:
|
||||
list: &artist_album_title
|
||||
spacing: 0
|
||||
items:
|
||||
- map:
|
||||
conditions:
|
||||
state == playing: {string: {text: "{artist}"}}
|
||||
state == paused: {string: {text: "{artist}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
conditions:
|
||||
state == playing: {string: {text: "{album}"}}
|
||||
state == paused: {string: {text: "{album}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
conditions:
|
||||
state == playing: {string: {text: "{title}", foreground: ffa0a0ff}}
|
||||
state == paused: {string: {text: "{title}", foreground: ffffff66}}
|
||||
|
||||
content:
|
||||
map:
|
||||
margin: 10
|
||||
conditions:
|
||||
state == offline: {string: {text: offline, foreground: ff0000ff}}
|
||||
state == stopped: {string: {text: stopped}}
|
||||
state == paused: {list: *artist_album_title}
|
||||
state == playing: {list: *artist_album_title}
|
||||
|
||||
right:
|
||||
- removables:
|
||||
anchors:
|
||||
drive: &drive { text: , font: *awesome}
|
||||
optical: &optical {text: , font: *awesome}
|
||||
spacing: 5
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~mounted:
|
||||
map:
|
||||
on-click: udisksctl mount -b {device}
|
||||
conditions:
|
||||
~optical: [{string: *drive}, {string: {text: "{label}"}}]
|
||||
optical: [{string: *optical}, {string: {text: "{label}"}}]
|
||||
mounted:
|
||||
map:
|
||||
on-click: udisksctl unmount -b {device}
|
||||
conditions:
|
||||
~optical:
|
||||
- string: {<<: *drive, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
optical:
|
||||
- string: {<<: *optical, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
- sway-xkb:
|
||||
identifiers: [1:1:AT_Translated_Set_2_keyboard]
|
||||
content:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{layout}"}
|
||||
- network:
|
||||
content:
|
||||
map:
|
||||
default: {empty: {}}
|
||||
conditions:
|
||||
name == enp1s0:
|
||||
map:
|
||||
conditions:
|
||||
~carrier: {empty: {}}
|
||||
carrier:
|
||||
map:
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
state == up && ipv4 != "": {string: {text: , font: *awesome}}
|
||||
- network:
|
||||
poll-interval: 1000
|
||||
content:
|
||||
map:
|
||||
default: {empty: {}}
|
||||
conditions:
|
||||
name == wlp2s0:
|
||||
map:
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
state == down: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
state == up:
|
||||
map:
|
||||
default:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s"}
|
||||
|
||||
conditions:
|
||||
ipv4 == "":
|
||||
- string: {text: , font: *awesome, foreground: ffffff66}
|
||||
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s", foreground: ffffff66}
|
||||
- alsa:
|
||||
card: hw:PCH
|
||||
mixer: Master
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~online: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
online:
|
||||
map:
|
||||
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
|
||||
conditions:
|
||||
muted: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
~muted:
|
||||
ramp:
|
||||
tag: percent
|
||||
items:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- backlight:
|
||||
name: intel_backlight
|
||||
content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}]
|
||||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30000
|
||||
anchors:
|
||||
discharging: &discharging
|
||||
list:
|
||||
items:
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
- string: {text: , foreground: ff0000ff, font: *awesome}
|
||||
- string: {text: , foreground: ffa600ff, font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
state == unknown:
|
||||
<<: *discharging
|
||||
state == discharging:
|
||||
<<: *discharging
|
||||
state == charging:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
state == full:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% full"}
|
||||
state == "not charging":
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
- string: {text: , foreground: ff0000ff, font: *awesome}
|
||||
- string: {text: , foreground: ffa600ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}%"}
|
||||
- clock:
|
||||
time-format: "%H:%M %Z"
|
||||
content:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{date}", right-margin: 5}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{time}"}
|
||||
- label:
|
||||
content:
|
||||
string:
|
||||
on-click: systemctl poweroff
|
||||
text:
|
||||
font: *awesome
|
58
examples/configurations/river-tags.conf
Normal file
58
examples/configurations/river-tags.conf
Normal file
|
@ -0,0 +1,58 @@
|
|||
hack: &hack Hack Nerd Font:pixelsize=13
|
||||
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
|
||||
bar:
|
||||
height: 40
|
||||
location: top
|
||||
font: JuliaMono:pixelsize=10
|
||||
spacing: 2
|
||||
margin: 0
|
||||
layer: bottom
|
||||
foreground: eeeeeeff
|
||||
background: 2E3440dd
|
||||
|
||||
left:
|
||||
- river:
|
||||
anchors:
|
||||
- base: &river_base
|
||||
left-margin: 10
|
||||
right-margin: 13
|
||||
default: {string: {text: , font: *hack}}
|
||||
conditions:
|
||||
id == 1: {string: {text: ﳐ, font: *hack}}
|
||||
id == 2: {string: {text: , font: *hack}}
|
||||
id == 3: {string: {text: , font: *hack}}
|
||||
id == 4: {string: {text: , font: *hack}}
|
||||
id == 5: {string: {text: , font: *hack}}
|
||||
id == 10: {string: {text: "scratchpad", font: *hack}}
|
||||
id == 11: {string: {text: "work", font: *hack}}
|
||||
|
||||
content:
|
||||
map:
|
||||
on-click:
|
||||
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
|
||||
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
|
||||
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
|
||||
conditions:
|
||||
state == urgent:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {background: {color: D08770ff}}
|
||||
state == focused:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == visible && ~occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == visible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == unfocused:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == invisible && ~occupied: {empty: {}}
|
||||
state == invisible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {underline: {size: 3, color: ea6962ff}}
|
|
@ -1,273 +0,0 @@
|
|||
# Typical laptop setup, with wifi, brightness, battery etc, for
|
||||
# i3/Sway.
|
||||
|
||||
# For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and
|
||||
# the sway-xkb module with the xkb module.
|
||||
|
||||
# fonts we'll be re-using here and there
|
||||
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
|
||||
awesome_brands: &awesome_brands Font Awesome 5 Brands:pixelsize=16
|
||||
|
||||
std_underline: &std_underline {underline: { size: 2, color: ff0000ff}}
|
||||
|
||||
# This is THE bar configuration
|
||||
bar:
|
||||
height: 26
|
||||
location: top
|
||||
spacing: 5
|
||||
margin: 7
|
||||
|
||||
# Default font
|
||||
font: Adobe Helvetica:pixelsize=12
|
||||
|
||||
foreground: ffffffff
|
||||
background: 111111cc
|
||||
|
||||
border:
|
||||
width: 1
|
||||
color: 999999cc
|
||||
margin: 5
|
||||
top-margin: 0
|
||||
|
||||
left:
|
||||
- i3:
|
||||
anchors: # Not used (directly) by f00bar; used here to avoid duplication
|
||||
- string: &i3_common {margin: 5, on-click: "swaymsg --quiet workspace {name}"}
|
||||
- string: &default {<<: *i3_common, text: "? {name}"}
|
||||
- string: &main {<<: *i3_common, text: , font: *awesome}
|
||||
- string: &surfing {<<: *i3_common, text: , font: *awesome_brands}
|
||||
- string: &misc {<<: *i3_common, text: , font: *awesome}
|
||||
- string: &mail {<<: *i3_common, text: , font: *awesome}
|
||||
- string: &music {<<: *i3_common, text: , font: *awesome}
|
||||
- focused: &focused
|
||||
deco: {stack: [background: {color: ffa0a04c}, <<: *std_underline]}
|
||||
- invisible: &invisible {foreground: ffffff55}
|
||||
- urgent: &urgent
|
||||
foreground: 000000ff
|
||||
deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]}
|
||||
content:
|
||||
"":
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*default, *focused]}}
|
||||
unfocused: {string: {<<: *default}}
|
||||
invisible: {string: {<<: [*default, *invisible]}}
|
||||
urgent: {string: {<<: [*default, *urgent]}}
|
||||
main:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*main, *focused]}}
|
||||
unfocused: {string: {<<: *main}}
|
||||
invisible: {string: {<<: [*main, *invisible]}}
|
||||
urgent: {string: {<<: [*main, *urgent]}}
|
||||
surfing:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*surfing, *focused]}}
|
||||
unfocused: {string: {<<: *surfing}}
|
||||
invisible: {string: {<<: [*surfing, *invisible]}}
|
||||
urgent: {string: {<<: [*surfing, *urgent]}}
|
||||
misc:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*misc, *focused]}}
|
||||
unfocused: {string: {<<: *misc}}
|
||||
invisible: {string: {<<: [*misc, *invisible]}}
|
||||
urgent: {string: {<<: [*misc, *urgent]}}
|
||||
|
||||
mail:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*mail, *focused]}}
|
||||
unfocused: {string: {<<: *mail}}
|
||||
invisible: {string: {<<: [*mail, *invisible]}}
|
||||
urgent: {string: {<<: [*mail, *urgent]}}
|
||||
music:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*music, *focused]}}
|
||||
unfocused: {string: {<<: *music}}
|
||||
invisible: {string: {<<: [*music, *invisible]}}
|
||||
urgent: {string: {<<: [*music, *urgent]}}
|
||||
current:
|
||||
map:
|
||||
left-margin: 7
|
||||
tag: application
|
||||
values:
|
||||
"": {string: {text: "{title}"}}
|
||||
default:
|
||||
list:
|
||||
spacing: 0
|
||||
items:
|
||||
- string: {text: "{application}", max: 10, foreground: ffa0a0ff}
|
||||
- string: {text: ": "}
|
||||
- string: {text: "{title}", max: 35}
|
||||
|
||||
center:
|
||||
- mpd:
|
||||
host: /run/mpd/socket
|
||||
anchors:
|
||||
list: &artist_album_title
|
||||
spacing: 0
|
||||
items:
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{artist}"}}
|
||||
paused: {string: {text: "{artist}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{album}"}}
|
||||
paused: {string: {text: "{album}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{title}", foreground: ffa0a0ff}}
|
||||
paused: {string: {text: "{title}", foreground: ffffff66}}
|
||||
|
||||
content:
|
||||
map:
|
||||
margin: 10
|
||||
tag: state
|
||||
values:
|
||||
offline: {string: {text: offline, foreground: ff0000ff}}
|
||||
stopped: {string: {text: stopped}}
|
||||
paused: {list: *artist_album_title}
|
||||
playing: {list: *artist_album_title}
|
||||
|
||||
right:
|
||||
- removables:
|
||||
anchors:
|
||||
drive: &drive { text: , font: *awesome}
|
||||
optical: &optical {text: , font: *awesome}
|
||||
spacing: 5
|
||||
content:
|
||||
map:
|
||||
tag: mounted
|
||||
values:
|
||||
false:
|
||||
map:
|
||||
tag: optical
|
||||
on-click: udisksctl mount -b {device}
|
||||
values:
|
||||
false: [{string: *drive}, {string: {text: "{label}"}}]
|
||||
true: [{string: *optical}, {string: {text: "{label}"}}]
|
||||
true:
|
||||
map:
|
||||
tag: optical
|
||||
on-click: udisksctl unmount -b {device}
|
||||
values:
|
||||
false:
|
||||
- string: {<<: *drive, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
true:
|
||||
- string: {<<: *optical, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
- sway-xkb:
|
||||
identifiers: [1:1:AT_Translated_Set_2_keyboard]
|
||||
content:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{layout}"}
|
||||
- network:
|
||||
name: enp1s0
|
||||
content:
|
||||
map:
|
||||
tag: carrier
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
- network:
|
||||
name: wlp2s0
|
||||
content:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
down: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
- alsa:
|
||||
card: hw:PCH
|
||||
mixer: Master
|
||||
content:
|
||||
map:
|
||||
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
|
||||
tag: muted
|
||||
values:
|
||||
true: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
false:
|
||||
ramp:
|
||||
tag: volume
|
||||
items:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- backlight:
|
||||
name: intel_backlight
|
||||
content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}]
|
||||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30
|
||||
content:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
discharging:
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
- string: {text: , foreground: ff0000ff, font: *awesome}
|
||||
- string: {text: , foreground: ffa600ff, font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
charging:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
full:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% full"}
|
||||
- clock:
|
||||
time-format: "%H:%M %Z"
|
||||
content:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{date}", right-margin: 5}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{time}"}
|
||||
- label:
|
||||
content:
|
||||
string:
|
||||
on-click: loginctl poweroff
|
||||
text:
|
||||
font: *awesome
|
69
examples/river-minimal.yml
Normal file
69
examples/river-minimal.yml
Normal file
|
@ -0,0 +1,69 @@
|
|||
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
|
||||
bar:
|
||||
height: 32
|
||||
location: top
|
||||
background: 000000ff
|
||||
font: NotoSans:pixelsize=16
|
||||
|
||||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{date}", right-margin: 5}
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{time} "}
|
||||
left:
|
||||
- river:
|
||||
anchors:
|
||||
- base: &river_base
|
||||
left-margin: 10
|
||||
right-margin: 13
|
||||
default: {string: {text: }}
|
||||
conditions:
|
||||
id == 1: {string: {text: 1}}
|
||||
id == 2: {string: {text: 2}}
|
||||
id == 3: {string: {text: 3}}
|
||||
id == 4: {string: {text: 4}}
|
||||
id == 5: {string: {text: 5}}
|
||||
|
||||
content:
|
||||
map:
|
||||
on-click:
|
||||
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
|
||||
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
|
||||
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
|
||||
conditions:
|
||||
state == urgent:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {background: {color: D08770ff}}
|
||||
state == focused:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == visible && ~occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == visible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == unfocused:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == invisible && ~occupied: {empty: {}}
|
||||
state == invisible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {underline: {size: 3, color: ea6962ff}}
|
||||
|
||||
|
||||
center:
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: " {app-id}", foreground: ffa0a0ff}
|
||||
- string: {text: ": {title}"}
|
124
examples/scripts/cpu.sh
Executable file
124
examples/scripts/cpu.sh
Executable file
|
@ -0,0 +1,124 @@
|
|||
#!/bin/bash
|
||||
|
||||
# cpu.sh - measures CPU usage at a configurable sample interval
|
||||
#
|
||||
# Usage: cpu.sh INTERVAL_IN_SECONDS
|
||||
#
|
||||
# This script will emit the following tags on stdout (N is the number
|
||||
# of logical CPUs):
|
||||
#
|
||||
# Name Type
|
||||
# --------------------
|
||||
# cpu range 0-100
|
||||
# cpu0 range 0-100
|
||||
# cpu1 range 0-100
|
||||
# ...
|
||||
# cpuN-1 range 0-100
|
||||
#
|
||||
# I.e. ‘cpu’ is the average (or aggregated) CPU usage, while cpuX is a
|
||||
# specific CPU’s usage.
|
||||
#
|
||||
# Example configuration (update every second):
|
||||
#
|
||||
# - script:
|
||||
# path: /path/to/cpu.sh
|
||||
# args: [1]
|
||||
# content: {string: {text: "{cpu}%"}}
|
||||
#
|
||||
|
||||
interval=${1}
|
||||
|
||||
case ${interval} in
|
||||
''|*[!0-9]*)
|
||||
echo "interval must be an integer"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
# Get number of CPUs, by reading /proc/stat
|
||||
# The output looks like:
|
||||
#
|
||||
# cpu A B C D ...
|
||||
# cpu0 A B C D ...
|
||||
# cpu1 A B C D ...
|
||||
# cpuN A B C D ...
|
||||
#
|
||||
# The first line is a summary line, accounting *all* CPUs
|
||||
IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat)
|
||||
cpu_count=$((${#all_cpu_stats[@]} - 1))
|
||||
|
||||
# Arrays of ‘previous’ idle and total stats, needed to calculate the
|
||||
# difference between each sample.
|
||||
prev_idle=()
|
||||
prev_total=()
|
||||
for i in $(seq ${cpu_count}); do
|
||||
prev_idle+=(0)
|
||||
prev_total+=(0)
|
||||
done
|
||||
|
||||
prev_average_idle=0
|
||||
prev_average_total=0
|
||||
|
||||
while true; do
|
||||
IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat)
|
||||
|
||||
usage=() # CPU usage in percent, 0 <= x <= 100
|
||||
|
||||
average_idle=0 # All CPUs idle time since boot
|
||||
average_total=0 # All CPUs total time since boot
|
||||
|
||||
for i in $(seq 0 $((cpu_count - 1))); do
|
||||
# Split this CPUs stats into an array
|
||||
stats=($(echo "${all_cpu_stats[$((i + 1))]}"))
|
||||
|
||||
# man procfs(5)
|
||||
user=${stats[1]}
|
||||
nice=${stats[2]}
|
||||
system=${stats[3]}
|
||||
idle=${stats[4]}
|
||||
iowait=${stats[5]}
|
||||
irq=${stats[6]}
|
||||
softirq=${stats[7]}
|
||||
steal=${stats[8]}
|
||||
guest=${stats[9]}
|
||||
guestnice=${stats[10]}
|
||||
|
||||
# Guest time already accounted for in user
|
||||
user=$((user - guest))
|
||||
nice=$((nice - guestnice))
|
||||
|
||||
idle=$((idle + iowait))
|
||||
|
||||
total=$((user + nice + system + irq + softirq + idle + steal + guest + guestnice))
|
||||
|
||||
average_idle=$((average_idle + idle))
|
||||
average_total=$((average_total + total))
|
||||
|
||||
# Diff since last sample
|
||||
diff_idle=$((idle - prev_idle[i]))
|
||||
diff_total=$((total - prev_total[i]))
|
||||
|
||||
usage[i]=$((100 * (diff_total - diff_idle) / diff_total))
|
||||
|
||||
prev_idle[i]=${idle}
|
||||
prev_total[i]=${total}
|
||||
done
|
||||
|
||||
diff_average_idle=$((average_idle - prev_average_idle))
|
||||
diff_average_total=$((average_total - prev_average_total))
|
||||
|
||||
average_usage=$((100 * (diff_average_total - diff_average_idle) / diff_average_total))
|
||||
|
||||
prev_average_idle=${average_idle}
|
||||
prev_average_total=${average_total}
|
||||
|
||||
echo "cpu|range:0-100|${average_usage}"
|
||||
for i in $(seq 0 $((cpu_count - 1))); do
|
||||
echo "cpu${i}|range:0-100|${usage[i]}"
|
||||
done
|
||||
|
||||
echo ""
|
||||
sleep "${interval}"
|
||||
done
|
138
examples/scripts/dwl-tags.sh
Executable file
138
examples/scripts/dwl-tags.sh
Executable file
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# dwl-tags.sh - display dwl tags
|
||||
#
|
||||
# USAGE: dwl-tags.sh 1
|
||||
#
|
||||
# REQUIREMENTS:
|
||||
# - inotifywait ( 'inotify-tools' on arch )
|
||||
# - Launch dwl with `dwl > ~.cache/dwltags` or change $fname
|
||||
#
|
||||
# TAGS:
|
||||
# Name Type Return
|
||||
# ----------------------------------------------------
|
||||
# {tag_N} string dwl tags name
|
||||
# {tag_N_occupied} bool dwl tags state occupied
|
||||
# {tag_N_focused} bool dwl tags state focused
|
||||
# {layout} string dwl layout
|
||||
# {title} string client title
|
||||
#
|
||||
# Now the fun part
|
||||
#
|
||||
# Example configuration:
|
||||
#
|
||||
# - script:
|
||||
# path: /absolute/path/to/dwl-tags.sh
|
||||
# args: [1]
|
||||
# anchors:
|
||||
# - occupied: &occupied {foreground: 57bbf4ff}
|
||||
# - focused: &focused {foreground: fc65b0ff}
|
||||
# - default: &default {foreground: d2ccd6ff}
|
||||
# content:
|
||||
# - map:
|
||||
# margin: 4
|
||||
# conditions:
|
||||
# tag_0_occupied:
|
||||
# map:
|
||||
# conditions:
|
||||
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}}
|
||||
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *occupied}}
|
||||
# ~tag_0_occupied:
|
||||
# map:
|
||||
# conditions:
|
||||
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}}
|
||||
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *default}}
|
||||
# ...
|
||||
# ...
|
||||
# ...
|
||||
# - map:
|
||||
# margin: 4
|
||||
# conditions:
|
||||
# tag_8_occupied:
|
||||
# map:
|
||||
# conditions:
|
||||
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
|
||||
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *occupied}}
|
||||
# ~tag_8_occupied:
|
||||
# map:
|
||||
# values:
|
||||
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
|
||||
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *default}}
|
||||
# - list:
|
||||
# spacing: 3
|
||||
# items:
|
||||
# - string: {text: "{layout}"}
|
||||
# - string: {text: "{title}"}
|
||||
|
||||
|
||||
# Variables
|
||||
declare output title layout activetags selectedtags
|
||||
declare -a tags name
|
||||
readonly fname="$HOME"/.cache/dwltags
|
||||
|
||||
|
||||
_cycle() {
|
||||
tags=( "1" "2" "3" "4" "5" "6" "7" "8" "9" )
|
||||
|
||||
# Name of tag (optional)
|
||||
# If there is no name, number are used
|
||||
#
|
||||
# Example:
|
||||
# name=( "" "" "" "Media" )
|
||||
# -> return "" "" "" "Media" 5 6 7 8 9)
|
||||
name=()
|
||||
|
||||
for tag in "${!tags[@]}"; do
|
||||
mask=$((1<<tag))
|
||||
|
||||
tag_name="tag"
|
||||
declare "${tag_name}_${tag}"
|
||||
name[tag]="${name[tag]:-${tags[tag]}}"
|
||||
|
||||
printf -- '%s\n' "${tag_name}_${tag}|string|${name[tag]}"
|
||||
|
||||
if (( "${selectedtags}" & mask )) 2>/dev/null; then
|
||||
printf -- '%s\n' "${tag_name}_${tag}_focused|bool|true"
|
||||
printf -- '%s\n' "title|string|${title}"
|
||||
else
|
||||
printf '%s\n' "${tag_name}_${tag}_focused|bool|false"
|
||||
fi
|
||||
|
||||
if (( "${activetags}" & mask )) 2>/dev/null; then
|
||||
printf -- '%s\n' "${tag_name}_${tag}_occupied|bool|true"
|
||||
else
|
||||
printf -- '%s\n' "${tag_name}_${tag}_occupied|bool|false"
|
||||
fi
|
||||
done
|
||||
|
||||
printf -- '%s\n' "layout|string|${layout}"
|
||||
printf -- '%s\n' ""
|
||||
|
||||
}
|
||||
|
||||
# Call the function here so the tags are displayed at dwl launch
|
||||
_cycle
|
||||
|
||||
while true; do
|
||||
|
||||
[[ ! -f "${fname}" ]] && printf -- '%s\n' \
|
||||
"You need to redirect dwl stdout to ~/.cache/dwltags" >&2
|
||||
|
||||
inotifywait -qq --event modify "${fname}"
|
||||
|
||||
# Get info from the file
|
||||
output="$(tail -n6 "${fname}")"
|
||||
title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )"
|
||||
#selmon="$(echo "${output}" | grep 'selmon')"
|
||||
layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )"
|
||||
|
||||
# Get the tag bit mask as a decimal
|
||||
activetags="$(echo "${output}" | grep tags | awk '{print $3}')"
|
||||
selectedtags="$(echo "${output}" | grep tags | awk '{print $4}')"
|
||||
|
||||
_cycle
|
||||
|
||||
done
|
||||
|
||||
unset -v output title layout activetags selectedtags
|
||||
unset -v tags name
|
78
examples/scripts/pacman.sh
Executable file
78
examples/scripts/pacman.sh
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# pacman.sh - display number of packages update available
|
||||
# by default check every hour
|
||||
#
|
||||
# USAGE: pacman.sh
|
||||
#
|
||||
# TAGS:
|
||||
# Name Type Return
|
||||
# -------------------------------------------
|
||||
# {pacman} int number of pacman packages
|
||||
# {aur} int number of aur packages
|
||||
# {pkg} int sum of both
|
||||
#
|
||||
# Examples configuration:
|
||||
# - script:
|
||||
# path: /absolute/path/to/pacman.sh
|
||||
# args: []
|
||||
# content: { string: { text: "{pacman} + {aur} = {pkg}" } }
|
||||
#
|
||||
# To display a message when there is no update:
|
||||
# - script:
|
||||
# path: /absolute/path/to/pacman.sh
|
||||
# args: []
|
||||
# content:
|
||||
# map:
|
||||
# default: { string: { text: "{pacman} + {aur} = {pkg}" } }
|
||||
# conditions:
|
||||
# pkg == 0: {string: {text: no updates}}
|
||||
|
||||
|
||||
declare interval aur_helper pacman_num aur_num pkg_num
|
||||
|
||||
# Error message in STDERR
|
||||
_err() {
|
||||
printf -- '%s\n' "[$(date +'%Y-%m-%d %H:%M:%S')]: $*" >&2
|
||||
}
|
||||
|
||||
# Display tags before yambar fetch the updates number
|
||||
printf -- '%s\n' "pacman|int|0"
|
||||
printf -- '%s\n' "aur|int|0"
|
||||
printf -- '%s\n' "pkg|int|0"
|
||||
printf -- '%s\n' ""
|
||||
|
||||
|
||||
while true; do
|
||||
# Change interval
|
||||
# NUMBER[SUFFIXE]
|
||||
# Possible suffix:
|
||||
# "s" seconds / "m" minutes / "h" hours / "d" days
|
||||
interval="1h"
|
||||
|
||||
# Change your aur manager
|
||||
aur_helper="paru"
|
||||
|
||||
# Get number of packages to update
|
||||
pacman_num=$(checkupdates | wc -l)
|
||||
|
||||
if ! hash "${aur_helper}" >/dev/null 2>&1; then
|
||||
_err "aur helper not found, change it in the script"
|
||||
exit 1
|
||||
else
|
||||
aur_num=$("${aur_helper}" -Qmu | wc -l)
|
||||
fi
|
||||
|
||||
pkg_num=$(( pacman_num + aur_num ))
|
||||
|
||||
printf -- '%s\n' "pacman|int|${pacman_num}"
|
||||
printf -- '%s\n' "aur|int|${aur_num}"
|
||||
printf -- '%s\n' "pkg|int|${pkg_num}"
|
||||
printf -- '%s\n' ""
|
||||
|
||||
sleep "${interval}"
|
||||
|
||||
done
|
||||
|
||||
unset -v interval aur_helper pacman_num aur_num pkg_num
|
||||
unset -f _err
|
148
external/river-status-unstable-v1.xml
vendored
Normal file
148
external/river-status-unstable-v1.xml
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_status_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zriver_status_manager_v1" version="4">
|
||||
<description summary="manage river status objects">
|
||||
A global factory for objects that receive status information specific
|
||||
to river. It could be used to implement, for example, a status bar.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_status_manager object">
|
||||
This request indicates that the client will not use the
|
||||
river_status_manager object any more. Objects that have been created
|
||||
through this instance are not affected.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="get_river_output_status">
|
||||
<description summary="create an output status object">
|
||||
This creates a new river_output_status object for the given wl_output.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zriver_output_status_v1"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</request>
|
||||
|
||||
<request name="get_river_seat_status">
|
||||
<description summary="create a seat status object">
|
||||
This creates a new river_seat_status object for the given wl_seat.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_output_status_v1" version="4">
|
||||
<description summary="track output tags and focus">
|
||||
This interface allows clients to receive information about the current
|
||||
windowing state of an output.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_output_status object">
|
||||
This request indicates that the client will not use the
|
||||
river_output_status object any more.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="focused_tags">
|
||||
<description summary="focused tags of the output">
|
||||
Sent once binding the interface and again whenever the tag focus of
|
||||
the output changes.
|
||||
</description>
|
||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
||||
</event>
|
||||
|
||||
<event name="view_tags">
|
||||
<description summary="tag state of an output's views">
|
||||
Sent once on binding the interface and again whenever the tag state
|
||||
of the output changes.
|
||||
</description>
|
||||
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
|
||||
</event>
|
||||
|
||||
<event name="urgent_tags" since="2">
|
||||
<description summary="tags of the output with an urgent view">
|
||||
Sent once on binding the interface and again whenever the set of
|
||||
tags with at least one urgent view changes.
|
||||
</description>
|
||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
||||
</event>
|
||||
|
||||
<event name="layout_name" since="4">
|
||||
<description summary="name of the layout">
|
||||
Sent once on binding the interface should a layout name exist and again
|
||||
whenever the name changes.
|
||||
</description>
|
||||
<arg name="name" type="string" summary="layout name"/>
|
||||
</event>
|
||||
|
||||
<event name="layout_name_clear" since="4">
|
||||
<description summary="name of the layout">
|
||||
Sent when the current layout name has been removed without a new one
|
||||
being set, for example when the active layout generator disconnects.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_seat_status_v1" version="3">
|
||||
<description summary="track seat focus">
|
||||
This interface allows clients to receive information about the current
|
||||
focus of a seat. Note that (un)focused_output events will only be sent
|
||||
if the client has bound the relevant wl_output globals.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_seat_status object">
|
||||
This request indicates that the client will not use the
|
||||
river_seat_status object any more.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="focused_output">
|
||||
<description summary="the seat focused an output">
|
||||
Sent on binding the interface and again whenever an output gains focus.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="unfocused_output">
|
||||
<description summary="the seat unfocused an output">
|
||||
Sent whenever an output loses focus.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="focused_view">
|
||||
<description summary="information on the focused view">
|
||||
Sent once on binding the interface and again whenever the focused
|
||||
view or a property thereof changes. The title may be an empty string
|
||||
if no view is focused or the focused view did not set a title.
|
||||
</description>
|
||||
<arg name="title" type="string" summary="title of the focused view"/>
|
||||
</event>
|
||||
|
||||
<event name="mode" since="3">
|
||||
<description summary="the active mode changed">
|
||||
Sent once on binding the interface and again whenever a new mode
|
||||
is entered (e.g. with riverctl enter-mode foobar).
|
||||
</description>
|
||||
<arg name="name" type="string" summary="name of the mode"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
270
external/wlr-foreign-toplevel-management-unstable-v1.xml
vendored
Normal file
270
external/wlr-foreign-toplevel-management-unstable-v1.xml
vendored
Normal file
|
@ -0,0 +1,270 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Ilia Bozhinov
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
|
||||
<description summary="list and control opened apps">
|
||||
The purpose of this protocol is to enable the creation of taskbars
|
||||
and docks by providing them with a list of opened applications and
|
||||
letting them request certain actions on them, like maximizing, etc.
|
||||
|
||||
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
|
||||
toplevel window will be sent via the toplevel event
|
||||
</description>
|
||||
|
||||
<event name="toplevel">
|
||||
<description summary="a toplevel has been created">
|
||||
This event is emitted whenever a new toplevel window is created. It
|
||||
is emitted for all toplevels, regardless of the app that has created
|
||||
them.
|
||||
|
||||
All initial details of the toplevel(title, app_id, states, etc.) will
|
||||
be sent immediately after this event via the corresponding events in
|
||||
zwlr_foreign_toplevel_handle_v1.
|
||||
</description>
|
||||
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
Indicates the client no longer wishes to receive events for new toplevels.
|
||||
However the compositor may emit further toplevel_created events, until
|
||||
the finished event is emitted.
|
||||
|
||||
The client must not send any more requests after this one.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the compositor has finished with the toplevel manager">
|
||||
This event indicates that the compositor is done sending events to the
|
||||
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
|
||||
immediately after sending this request, so it will become invalid and
|
||||
the client should free any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
|
||||
<description summary="an opened toplevel">
|
||||
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
|
||||
window. Each app may have multiple opened toplevels.
|
||||
|
||||
Each toplevel has a list of outputs it is visible on, conveyed to the
|
||||
client with the output_enter and output_leave events.
|
||||
</description>
|
||||
|
||||
<event name="title">
|
||||
<description summary="title change">
|
||||
This event is emitted whenever the title of the toplevel changes.
|
||||
</description>
|
||||
<arg name="title" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="app_id">
|
||||
<description summary="app-id change">
|
||||
This event is emitted whenever the app-id of the toplevel changes.
|
||||
</description>
|
||||
<arg name="app_id" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="toplevel entered an output">
|
||||
This event is emitted whenever the toplevel becomes visible on
|
||||
the given output. A toplevel may be visible on multiple outputs.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="toplevel left an output">
|
||||
This event is emitted whenever the toplevel stops being visible on
|
||||
the given output. It is guaranteed that an entered-output event
|
||||
with the same output has been emitted before this event.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<request name="set_maximized">
|
||||
<description summary="requests that the toplevel be maximized">
|
||||
Requests that the toplevel be maximized. If the maximized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="unset_maximized">
|
||||
<description summary="requests that the toplevel be unmaximized">
|
||||
Requests that the toplevel be unmaximized. If the maximized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_minimized">
|
||||
<description summary="requests that the toplevel be minimized">
|
||||
Requests that the toplevel be minimized. If the minimized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="unset_minimized">
|
||||
<description summary="requests that the toplevel be unminimized">
|
||||
Requests that the toplevel be unminimized. If the minimized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the toplevel">
|
||||
Request that this toplevel be activated on the given seat.
|
||||
There is no guarantee the toplevel will be actually activated.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the toplevel">
|
||||
The different states that a toplevel can have. These have the same meaning
|
||||
as the states with the same names defined in xdg-toplevel
|
||||
</description>
|
||||
|
||||
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
|
||||
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
|
||||
<entry name="activated" value="2" summary="the toplevel is active"/>
|
||||
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
|
||||
</enum>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the toplevel state changed">
|
||||
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
|
||||
is created and each time the toplevel state changes, either because of a
|
||||
compositor action or because of a request in this protocol.
|
||||
</description>
|
||||
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the toplevel has been sent">
|
||||
This event is sent after all changes in the toplevel state have been
|
||||
sent.
|
||||
|
||||
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
|
||||
to be seen as atomic, even if they happen via multiple events.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="close">
|
||||
<description summary="request that the toplevel be closed">
|
||||
Send a request to the toplevel to close itself. The compositor would
|
||||
typically use a shell-specific method to carry out this request, for
|
||||
example by sending the xdg_toplevel.close event. However, this gives
|
||||
no guarantees the toplevel will actually be destroyed. If and when
|
||||
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
|
||||
be emitted.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_rectangle">
|
||||
<description summary="the rectangle which represents the toplevel">
|
||||
The rectangle of the surface specified in this request corresponds to
|
||||
the place where the app using this protocol represents the given toplevel.
|
||||
It can be used by the compositor as a hint for some operations, e.g
|
||||
minimizing. The client is however not required to set this, in which
|
||||
case the compositor is free to decide some default value.
|
||||
|
||||
If the client specifies more than one rectangle, only the last one is
|
||||
considered.
|
||||
|
||||
The dimensions are given in surface-local coordinates.
|
||||
Setting width=height=0 removes the already-set rectangle.
|
||||
</description>
|
||||
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="x" type="int"/>
|
||||
<arg name="y" type="int"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_rectangle" value="0"
|
||||
summary="the provided rectangle is invalid"/>
|
||||
</enum>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="this toplevel has been destroyed">
|
||||
This event means the toplevel has been destroyed. It is guaranteed there
|
||||
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
|
||||
toplevel itself becomes inert so any requests will be ignored except the
|
||||
destroy request.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
|
||||
Destroys the zwlr_foreign_toplevel_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the toplevel anymore or after the closed event to finalize the
|
||||
destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_fullscreen" since="2">
|
||||
<description summary="request that the toplevel be fullscreened">
|
||||
Requests that the toplevel be fullscreened on the given output. If the
|
||||
fullscreen state and/or the outputs the toplevel is visible on actually
|
||||
change, this will be indicated by the state and output_enter/leave
|
||||
events.
|
||||
|
||||
The output parameter is only a hint to the compositor. Also, if output
|
||||
is NULL, the compositor should decide which output the toplevel will be
|
||||
fullscreened on, if at all.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<request name="unset_fullscreen" since="2">
|
||||
<description summary="request that the toplevel be unfullscreened">
|
||||
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||
actually changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<event name="parent" since="3">
|
||||
<description summary="parent change">
|
||||
This event is emitted whenever the parent of the toplevel changes.
|
||||
|
||||
No event is emitted when the parent handle is destroyed by the client.
|
||||
</description>
|
||||
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
109
external/wlr-layer-shell-unstable-v1.xml
vendored
109
external/wlr-layer-shell-unstable-v1.xml
vendored
|
@ -25,7 +25,7 @@
|
|||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_layer_shell_v1" version="2">
|
||||
<interface name="zwlr_layer_shell_v1" version="4">
|
||||
<description summary="create surfaces that are layers of the desktop">
|
||||
Clients can use this interface to assign the surface_layer role to
|
||||
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
|
@ -47,6 +47,12 @@
|
|||
or manipulate a buffer prior to the first layer_surface.configure call
|
||||
must also be treated as errors.
|
||||
|
||||
After creating a layer_surface object and setting it up, the client
|
||||
must perform an initial commit without any buffer attached.
|
||||
The compositor will reply with a layer_surface.configure event.
|
||||
The client must acknowledge it and is then allowed to attach a buffer
|
||||
to map the surface.
|
||||
|
||||
You may pass NULL for output to allow the compositor to decide which
|
||||
output to use. Generally this will be the one that the user most
|
||||
recently interacted with.
|
||||
|
@ -82,9 +88,19 @@
|
|||
<entry name="top" value="2"/>
|
||||
<entry name="overlay" value="3"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="destroy" type="destructor" since="3">
|
||||
<description summary="destroy the layer_shell object">
|
||||
This request indicates that the client will not use the layer_shell
|
||||
object any more. Objects that have been created through this instance
|
||||
are not affected.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_layer_surface_v1" version="2">
|
||||
<interface name="zwlr_layer_surface_v1" version="4">
|
||||
<description summary="layer metadata interface">
|
||||
An interface that may be implemented by a wl_surface, for surfaces that
|
||||
are designed to be rendered as a layer of a stacked desktop-like
|
||||
|
@ -93,6 +109,14 @@
|
|||
Layer surface state (layer, size, anchor, exclusive zone,
|
||||
margin, interactivity) is double-buffered, and will be applied at the
|
||||
time wl_surface.commit of the corresponding wl_surface is called.
|
||||
|
||||
Attaching a null buffer to a layer surface unmaps it.
|
||||
|
||||
Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
compositor until it is explicitly mapped again. The layer_surface
|
||||
returns to the state it had right after layer_shell.get_layer_surface.
|
||||
The client can re-map the surface by performing a commit without any
|
||||
buffer attached, waiting for a configure event and handling it as usual.
|
||||
</description>
|
||||
|
||||
<request name="set_size">
|
||||
|
@ -179,21 +203,85 @@
|
|||
<arg name="left" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="keyboard_interactivity">
|
||||
<description summary="types of keyboard interaction possible for a layer shell surface">
|
||||
Types of keyboard interaction possible for layer shell surfaces. The
|
||||
rationale for this is twofold: (1) some applications are not interested
|
||||
in keyboard events and not allowing them to be focused can improve the
|
||||
desktop experience; (2) some applications will want to take exclusive
|
||||
keyboard focus.
|
||||
</description>
|
||||
|
||||
<entry name="none" value="0">
|
||||
<description summary="no keyboard focus is possible">
|
||||
This value indicates that this surface is not interested in keyboard
|
||||
events and the compositor should never assign it the keyboard focus.
|
||||
|
||||
This is the default value, set for newly created layer shell surfaces.
|
||||
|
||||
This is useful for e.g. desktop widgets that display information or
|
||||
only have interaction with non-keyboard input devices.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="exclusive" value="1">
|
||||
<description summary="request exclusive keyboard focus">
|
||||
Request exclusive keyboard focus if this surface is above the shell surface layer.
|
||||
|
||||
For the top and overlay layers, the seat will always give
|
||||
exclusive keyboard focus to the top-most layer which has keyboard
|
||||
interactivity set to exclusive. If this layer contains multiple
|
||||
surfaces with keyboard interactivity set to exclusive, the compositor
|
||||
determines the one receiving keyboard events in an implementation-
|
||||
defined manner. In this case, no guarantee is made when this surface
|
||||
will receive keyboard focus (if ever).
|
||||
|
||||
For the bottom and background layers, the compositor is allowed to use
|
||||
normal focus semantics.
|
||||
|
||||
This setting is mainly intended for applications that need to ensure
|
||||
they receive all keyboard events, such as a lock screen or a password
|
||||
prompt.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="on_demand" value="2" since="4">
|
||||
<description summary="request regular keyboard focus semantics">
|
||||
This requests the compositor to allow this surface to be focused and
|
||||
unfocused by the user in an implementation-defined manner. The user
|
||||
should be able to unfocus this surface even regardless of the layer
|
||||
it is on.
|
||||
|
||||
Typically, the compositor will want to use its normal mechanism to
|
||||
manage keyboard focus between layer shell surfaces with this setting
|
||||
and regular toplevels on the desktop layer (e.g. click to focus).
|
||||
Nevertheless, it is possible for a compositor to require a special
|
||||
interaction to focus or unfocus layer shell surfaces (e.g. requiring
|
||||
a click even if focus follows the mouse normally, or providing a
|
||||
keybinding to switch focus between layers).
|
||||
|
||||
This setting is mainly intended for desktop shell components (e.g.
|
||||
panels) that allow keyboard interaction. Using this option can allow
|
||||
implementing a desktop shell that can be fully usable without the
|
||||
mouse.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<request name="set_keyboard_interactivity">
|
||||
<description summary="requests keyboard events">
|
||||
Set to 1 to request that the seat send keyboard events to this layer
|
||||
surface. For layers below the shell surface layer, the seat will use
|
||||
normal focus semantics. For layers above the shell surface layers, the
|
||||
seat will always give exclusive keyboard focus to the top-most layer
|
||||
which has keyboard interactivity set to true.
|
||||
Set how keyboard events are delivered to this surface. By default,
|
||||
layer shell surfaces do not receive keyboard events; this request can
|
||||
be used to change this.
|
||||
|
||||
This setting is inherited by child surfaces set by the get_popup
|
||||
request.
|
||||
|
||||
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
you do not want to receive them, set the input region on your surface
|
||||
to an empty region.
|
||||
|
||||
Events is double-buffered, see wl_surface.commit.
|
||||
Keyboard interactivity is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="keyboard_interactivity" type="uint"/>
|
||||
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
|
||||
</request>
|
||||
|
||||
<request name="get_popup">
|
||||
|
@ -278,6 +366,7 @@
|
|||
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
|
||||
</enum>
|
||||
|
||||
<enum name="anchor" bitfield="true">
|
||||
|
@ -295,7 +384,7 @@
|
|||
|
||||
Layer is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="layer" type="uint" enum="layer" summary="layer to move this surface to"/>
|
||||
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
2
external/wlr-protocols
vendored
2
external/wlr-protocols
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 29d4a59df8cbbc719fea9fe84689a45569410a86
|
||||
Subproject commit d1598e82240d6e8ca57729495a94d4e11d222033
|
7
font-shaping.h
Normal file
7
font-shaping.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
enum font_shaping {
|
||||
FONT_SHAPE_NONE,
|
||||
FONT_SHAPE_GRAPHEMES,
|
||||
FONT_SHAPE_FULL,
|
||||
};
|
|
@ -13,11 +13,18 @@ out_file=${3}
|
|||
if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then
|
||||
workdir=$(pwd)
|
||||
cd "${src_dir}"
|
||||
git_version=$(git describe --always --tags)
|
||||
|
||||
if git describe --tags > /dev/null 2>&1; then
|
||||
git_version=$(git describe --always --tags)
|
||||
else
|
||||
# No tags available, happens in e.g. CI builds
|
||||
git_version="${default_version}"
|
||||
fi
|
||||
|
||||
git_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
cd "${workdir}"
|
||||
|
||||
new_version="${git_version} ($(env LC_TIME=C date "+%b %d %Y"), branch '${git_branch}')"
|
||||
new_version="${git_version} ($(date "+%b %d %Y"), branch '${git_branch}')"
|
||||
else
|
||||
new_version="${default_version}"
|
||||
fi
|
||||
|
|
258
log.c
258
log.c
|
@ -1,41 +1,60 @@
|
|||
#include "log.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <syslog.h>
|
||||
#define ALEN(v) (sizeof(v) / sizeof((v)[0]))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
static bool colorize = false;
|
||||
static bool do_syslog = true;
|
||||
static bool do_syslog = false;
|
||||
static enum log_class log_level = LOG_CLASS_NONE;
|
||||
|
||||
static const struct {
|
||||
const char name[8];
|
||||
const char log_prefix[7];
|
||||
uint8_t color;
|
||||
int syslog_equivalent;
|
||||
} log_level_map[] = {
|
||||
[LOG_CLASS_NONE] = {"none", "none", 5, -1},
|
||||
[LOG_CLASS_ERROR] = {"error", " err", 31, LOG_ERR},
|
||||
[LOG_CLASS_WARNING] = {"warning", "warn", 33, LOG_WARNING},
|
||||
[LOG_CLASS_INFO] = {"info", "info", 97, LOG_INFO},
|
||||
[LOG_CLASS_DEBUG] = {"debug", " dbg", 36, LOG_DEBUG},
|
||||
};
|
||||
|
||||
void
|
||||
log_init(enum log_colorize _colorize, bool _do_syslog,
|
||||
enum log_facility syslog_facility, enum log_class syslog_level)
|
||||
log_init(enum log_colorize _colorize, bool _do_syslog, enum log_facility syslog_facility, enum log_class _log_level)
|
||||
{
|
||||
static const int facility_map[] = {
|
||||
[LOG_FACILITY_USER] = LOG_USER,
|
||||
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
|
||||
};
|
||||
|
||||
static const int level_map[] = {
|
||||
[LOG_CLASS_ERROR] = LOG_ERR,
|
||||
[LOG_CLASS_WARNING] = LOG_WARNING,
|
||||
[LOG_CLASS_INFO] = LOG_INFO,
|
||||
[LOG_CLASS_DEBUG] = LOG_DEBUG,
|
||||
};
|
||||
/* Don't use colors if NO_COLOR is defined and not empty */
|
||||
const char *no_color_str = getenv("NO_COLOR");
|
||||
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
|
||||
|
||||
colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO);
|
||||
colorize = _colorize == LOG_COLORIZE_NEVER
|
||||
? false
|
||||
: _colorize == LOG_COLORIZE_ALWAYS
|
||||
? true
|
||||
: !no_color && isatty(STDERR_FILENO);
|
||||
do_syslog = _do_syslog;
|
||||
log_level = _log_level;
|
||||
|
||||
if (do_syslog) {
|
||||
openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]);
|
||||
setlogmask(LOG_UPTO(level_map[syslog_level]));
|
||||
int slvl = log_level_map[_log_level].syslog_equivalent;
|
||||
if (do_syslog && slvl != -1) {
|
||||
openlog(NULL, /*LOG_PID*/ 0, facility_map[syslog_facility]);
|
||||
setlogmask(LOG_UPTO(slvl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,120 +66,153 @@ log_deinit(void)
|
|||
}
|
||||
|
||||
static void
|
||||
_log(enum log_class log_class, const char *module, const char *file, int lineno,
|
||||
const char *fmt, int sys_errno, va_list va)
|
||||
_log(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, int sys_errno,
|
||||
va_list va)
|
||||
{
|
||||
const char *class = "abcd";
|
||||
int class_clr = 0;
|
||||
switch (log_class) {
|
||||
case LOG_CLASS_ERROR: class = " err"; class_clr = 31; break;
|
||||
case LOG_CLASS_WARNING: class = "warn"; class_clr = 33; break;
|
||||
case LOG_CLASS_INFO: class = "info"; class_clr = 97; break;
|
||||
case LOG_CLASS_DEBUG: class = " dbg"; class_clr = 36; break;
|
||||
}
|
||||
assert(log_class > LOG_CLASS_NONE);
|
||||
assert(log_class < ALEN(log_level_map));
|
||||
|
||||
if (log_class > log_level)
|
||||
return;
|
||||
|
||||
const char *prefix = log_level_map[log_class].log_prefix;
|
||||
unsigned int class_clr = log_level_map[log_class].color;
|
||||
|
||||
char clr[16];
|
||||
snprintf(clr, sizeof(clr), "\e[%dm", class_clr);
|
||||
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", class, colorize ? "\e[0m" : "");
|
||||
snprintf(clr, sizeof(clr), "\033[%um", class_clr);
|
||||
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", prefix, colorize ? "\033[0m" : "");
|
||||
|
||||
if (colorize)
|
||||
fprintf(stderr, "\e[2m");
|
||||
fputs("\033[2m", stderr);
|
||||
fprintf(stderr, "%s:%d: ", file, lineno);
|
||||
if (colorize)
|
||||
fprintf(stderr, "\e[0m");
|
||||
fputs("\033[0m", stderr);
|
||||
|
||||
vfprintf(stderr, fmt, va);
|
||||
|
||||
if (sys_errno != 0)
|
||||
fprintf(stderr, ": %s", strerror(sys_errno));
|
||||
fprintf(stderr, ": %s (%d)", strerror(sys_errno), sys_errno);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
static void
|
||||
_sys_log(enum log_class log_class, const char *module,
|
||||
const char *file __attribute__((unused)),
|
||||
int lineno __attribute__((unused)),
|
||||
const char *fmt, int sys_errno, va_list va)
|
||||
_sys_log(enum log_class log_class, const char *module, const char UNUSED *file, int UNUSED lineno, const char *fmt,
|
||||
int sys_errno, va_list va)
|
||||
{
|
||||
assert(log_class > LOG_CLASS_NONE);
|
||||
assert(log_class < ALEN(log_level_map));
|
||||
|
||||
if (!do_syslog)
|
||||
return;
|
||||
|
||||
if (log_class > log_level)
|
||||
return;
|
||||
|
||||
/* Map our log level to syslog's level */
|
||||
int level = -1;
|
||||
switch (log_class) {
|
||||
case LOG_CLASS_ERROR: level = LOG_ERR; break;
|
||||
case LOG_CLASS_WARNING: level = LOG_WARNING; break;
|
||||
case LOG_CLASS_INFO: level = LOG_INFO; break;
|
||||
case LOG_CLASS_DEBUG: level = LOG_DEBUG; break;
|
||||
}
|
||||
int level = log_level_map[log_class].syslog_equivalent;
|
||||
|
||||
assert(level != -1);
|
||||
char msg[4096];
|
||||
int n = vsnprintf(msg, sizeof(msg), fmt, va);
|
||||
assert(n >= 0);
|
||||
|
||||
const char *sys_err = sys_errno != 0 ? strerror(sys_errno) : NULL;
|
||||
if (sys_errno != 0 && (size_t)n < sizeof(msg))
|
||||
snprintf(msg + n, sizeof(msg) - n, ": %s", strerror(sys_errno));
|
||||
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
|
||||
/* Calculate required size of buffer holding the entire log message */
|
||||
int required_len = 0;
|
||||
required_len += strlen(module) + 2; /* "%s: " */
|
||||
required_len += vsnprintf(NULL, 0, fmt, va2); va_end(va2);
|
||||
|
||||
if (sys_errno != 0)
|
||||
required_len += strlen(sys_err) + 2; /* ": %s" */
|
||||
|
||||
/* Format the msg */
|
||||
char *msg = malloc(required_len + 1);
|
||||
int idx = 0;
|
||||
|
||||
idx += snprintf(&msg[idx], required_len + 1 - idx, "%s: ", module);
|
||||
idx += vsnprintf(&msg[idx], required_len + 1 - idx, fmt, va);
|
||||
|
||||
if (sys_errno != 0) {
|
||||
snprintf(
|
||||
&msg[idx], required_len + 1 - idx, ": %s", strerror(sys_errno));
|
||||
}
|
||||
|
||||
syslog(level, "%s", msg);
|
||||
free(msg);
|
||||
syslog(level, "%s: %s", module, msg);
|
||||
}
|
||||
|
||||
void
|
||||
log_msg(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, const char *fmt, ...)
|
||||
log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, 0, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, 0, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
_log(log_class, module, file, lineno, fmt, 0, va);
|
||||
_sys_log(log_class, module, file, lineno, fmt, 0, va2);
|
||||
va_end(va2);
|
||||
}
|
||||
|
||||
void log_errno(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...)
|
||||
void
|
||||
log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, errno, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, errno, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
log_msg_va(log_class, module, file, lineno, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void log_errno_provided(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...)
|
||||
void
|
||||
log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, _errno, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, _errno, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
log_errno_provided_va(log_class, module, file, lineno, errno, fmt, va);
|
||||
}
|
||||
|
||||
void
|
||||
log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
log_errno_va(log_class, module, file, lineno, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void
|
||||
log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy,
|
||||
const char *fmt, va_list va)
|
||||
{
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
_log(log_class, module, file, lineno, fmt, errno_copy, va);
|
||||
_sys_log(log_class, module, file, lineno, fmt, errno_copy, va2);
|
||||
va_end(va2);
|
||||
}
|
||||
|
||||
void
|
||||
log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
log_errno_provided_va(log_class, module, file, lineno, errno_copy, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
static size_t
|
||||
map_len(void)
|
||||
{
|
||||
size_t len = ALEN(log_level_map);
|
||||
#ifndef _DEBUG
|
||||
/* Exclude "debug" entry for non-debug builds */
|
||||
len--;
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
int
|
||||
log_level_from_string(const char *str)
|
||||
{
|
||||
if (str[0] == '\0')
|
||||
return -1;
|
||||
|
||||
for (int i = 0, n = map_len(); i < n; i++)
|
||||
if (strcmp(str, log_level_map[i].name) == 0)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *
|
||||
log_level_string_hint(void)
|
||||
{
|
||||
static char buf[64];
|
||||
if (buf[0] != '\0')
|
||||
return buf;
|
||||
|
||||
for (size_t i = 0, pos = 0, n = map_len(); i < n; i++) {
|
||||
const char *entry = log_level_map[i].name;
|
||||
const char *delim = (i + 1 < n) ? ", " : "";
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "'%s'%s", entry, delim);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
|
55
log.h
55
log.h
|
@ -1,42 +1,43 @@
|
|||
#pragma once
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO };
|
||||
enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON };
|
||||
enum log_class { LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG };
|
||||
|
||||
void log_init(enum log_colorize colorize, bool do_syslog,
|
||||
enum log_facility syslog_facility, enum log_class syslog_level);
|
||||
enum log_class { LOG_CLASS_NONE, LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG };
|
||||
|
||||
void log_init(enum log_colorize colorize, bool do_syslog, enum log_facility syslog_facility, enum log_class log_level);
|
||||
void log_deinit(void);
|
||||
|
||||
void log_msg(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 5, 6)));
|
||||
void log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
__attribute__((format(printf, 5, 6)));
|
||||
|
||||
void log_errno(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 5, 6)));
|
||||
void log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
__attribute__((format(printf, 5, 6)));
|
||||
|
||||
void log_errno_provided(
|
||||
enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 6, 7)));
|
||||
void log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...) __attribute__((format(printf, 6, 7)));
|
||||
|
||||
#define LOG_ERR(fmt, ...) \
|
||||
log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_ERRNO(fmt, ...) \
|
||||
log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_ERRNO_P(fmt, _errno, ...) \
|
||||
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, \
|
||||
_errno, fmt, ## __VA_ARGS__)
|
||||
#define LOG_WARN(fmt, ...) \
|
||||
log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_INFO(fmt, ...) \
|
||||
log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
void log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
|
||||
__attribute__((format(printf, 5, 0)));
|
||||
void log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt,
|
||||
va_list va) __attribute__((format(printf, 5, 0)));
|
||||
void log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int _errno,
|
||||
const char *fmt, va_list va) __attribute__((format(printf, 6, 0)));
|
||||
|
||||
int log_level_from_string(const char *str);
|
||||
const char *log_level_string_hint(void);
|
||||
|
||||
#define LOG_ERR(...) log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LOG_ERRNO(...) log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LOG_ERRNO_P(_errno, ...) \
|
||||
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, _errno, __VA_ARGS__)
|
||||
#define LOG_WARN(...) log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LOG_INFO(...) log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
||||
#define LOG_DBG(fmt, ...) \
|
||||
log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_DBG(...) log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_DBG(fmt, ...)
|
||||
#define LOG_DBG(...)
|
||||
#endif
|
||||
|
|
104
main.c
104
main.c
|
@ -1,4 +1,6 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
|
@ -9,14 +11,12 @@
|
|||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "bar/bar.h"
|
||||
#include "config.h"
|
||||
|
@ -87,7 +87,7 @@ get_config_path(void)
|
|||
static struct bar *
|
||||
load_bar(const char *config_path, enum bar_backend backend)
|
||||
{
|
||||
FILE *conf_file = fopen(config_path, "r");
|
||||
FILE *conf_file = fopen(config_path, "re");
|
||||
if (conf_file == NULL) {
|
||||
LOG_ERRNO("%s: failed to open", config_path);
|
||||
return NULL;
|
||||
|
@ -127,13 +127,14 @@ print_usage(const char *prog_name)
|
|||
printf("Usage: %s [OPTION]...\n", prog_name);
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -b,--backend={xcb,wayland,auto} backend to use (default: auto)\n"
|
||||
" -c,--config=FILE alternative configuration file\n"
|
||||
" -C,--validate verify configuration then quit\n"
|
||||
" -p,--print-pid=FILE|FD print PID to file or FD\n"
|
||||
" -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
|
||||
" -s,--log-no-syslog disable syslog logging\n"
|
||||
" -v,--version show the version number and quit\n");
|
||||
printf(" -b,--backend={xcb,wayland,auto} backend to use (default: auto)\n"
|
||||
" -c,--config=FILE alternative configuration file\n"
|
||||
" -C,--validate verify configuration then quit\n"
|
||||
" -p,--print-pid=FILE|FD print PID to file or FD\n"
|
||||
" -d,--log-level={info|warning|error|none} log level (warning)\n"
|
||||
" -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
|
||||
" -s,--log-no-syslog disable syslog logging\n"
|
||||
" -v,--version show the version number and quit\n");
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -146,9 +147,8 @@ print_pid(const char *pid_file, bool *unlink_at_exit)
|
|||
int pid_fd = strtoul(pid_file, &end, 10);
|
||||
|
||||
if (errno != 0 || *end != '\0') {
|
||||
if ((pid_fd = open(pid_file,
|
||||
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
|
||||
if ((pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))
|
||||
< 0) {
|
||||
LOG_ERRNO("%s: failed to open", pid_file);
|
||||
return false;
|
||||
} else
|
||||
|
@ -176,18 +176,17 @@ print_pid(const char *pid_file, bool *unlink_at_exit)
|
|||
int
|
||||
main(int argc, char *const *argv)
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
|
||||
static const struct option longopts[] = {
|
||||
{"backend", required_argument, 0, 'b'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
{"validate", no_argument, 0, 'C'},
|
||||
{"print-pid", required_argument, 0, 'p'},
|
||||
{"log-colorize", optional_argument, 0, 'l'},
|
||||
{"log-no-syslog", no_argument, 0, 's'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{NULL, no_argument, 0, 0},
|
||||
{"backend", required_argument, 0, 'b'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
{"validate", no_argument, 0, 'C'},
|
||||
{"print-pid", required_argument, 0, 'p'},
|
||||
{"log-level", required_argument, 0, 'd'},
|
||||
{"log-colorize", optional_argument, 0, 'l'},
|
||||
{"log-no-syslog", no_argument, 0, 's'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{NULL, no_argument, 0, 0},
|
||||
};
|
||||
|
||||
bool unlink_pid_file = false;
|
||||
|
@ -197,11 +196,12 @@ main(int argc, char *const *argv)
|
|||
char *config_path = NULL;
|
||||
enum bar_backend backend = BAR_BACKEND_AUTO;
|
||||
|
||||
enum log_class log_level = LOG_CLASS_WARNING;
|
||||
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
|
||||
bool log_syslog = true;
|
||||
|
||||
while (true) {
|
||||
int c = getopt_long(argc, argv, ":b:c:Cp:l::svh", longopts, NULL);
|
||||
int c = getopt_long(argc, argv, ":b:c:Cp:d:l::svh", longopts, NULL);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
|
@ -222,9 +222,8 @@ main(int argc, char *const *argv)
|
|||
if (stat(optarg, &st) == -1) {
|
||||
fprintf(stderr, "%s: invalid configuration file: %s\n", optarg, strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
} else if (!S_ISREG(st.st_mode)) {
|
||||
fprintf(stderr, "%s: invalid configuration file: not a regular file\n",
|
||||
optarg);
|
||||
} else if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) {
|
||||
fprintf(stderr, "%s: invalid configuration file: neither a regular file nor a pipe or FIFO\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -240,6 +239,16 @@ main(int argc, char *const *argv)
|
|||
pid_file = optarg;
|
||||
break;
|
||||
|
||||
case 'd': {
|
||||
int lvl = log_level_from_string(optarg);
|
||||
if (lvl < 0) {
|
||||
fprintf(stderr, "-d,--log-level: %s: argument must be one of %s\n", optarg, log_level_string_hint());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
log_level = lvl;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'l':
|
||||
if (optarg == NULL || strcmp(optarg, "auto") == 0)
|
||||
log_colorize = LOG_COLORIZE_AUTO;
|
||||
|
@ -275,7 +284,12 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
}
|
||||
|
||||
log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, LOG_CLASS_WARNING);
|
||||
log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, log_level);
|
||||
|
||||
_Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR, "fcft log level enum offset");
|
||||
_Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch");
|
||||
fcft_init((enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level);
|
||||
atexit(&fcft_fini);
|
||||
|
||||
const struct sigaction sa = {.sa_handler = &signal_handler};
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
|
@ -321,12 +335,14 @@ main(int argc, char *const *argv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "");
|
||||
|
||||
bar->abort_fd = abort_fd;
|
||||
|
||||
thrd_t bar_thread;
|
||||
thrd_create(&bar_thread, (int (*)(void *))bar->run, bar);
|
||||
|
||||
/* Now unblock. We should be only thread receiving SIGINT */
|
||||
/* Now unblock. We should be only thread receiving SIGINT/SIGTERM */
|
||||
pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
|
||||
|
||||
if (pid_file != NULL) {
|
||||
|
@ -336,19 +352,21 @@ main(int argc, char *const *argv)
|
|||
|
||||
while (!aborted) {
|
||||
struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}};
|
||||
int r __attribute__((unused)) = poll(fds, 1, -1);
|
||||
int r __attribute__((unused)) = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
|
||||
/*
|
||||
* Either the bar aborted (triggering the abort_fd), or user
|
||||
* killed us (triggering the signal handler which sets
|
||||
* 'aborted')
|
||||
*/
|
||||
assert(aborted || r == 1);
|
||||
break;
|
||||
if (fds[0].revents & (POLLIN | POLLHUP)) {
|
||||
/*
|
||||
* Either the bar aborted (triggering the abort_fd), or user
|
||||
* killed us (triggering the signal handler which sets
|
||||
* 'aborted')
|
||||
*/
|
||||
assert(aborted || r == 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (aborted)
|
||||
LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted);
|
||||
LOG_INFO("aborted: %s (%ld)", strsignal(aborted), (long)aborted);
|
||||
|
||||
done:
|
||||
/* Signal abort to other threads */
|
||||
|
@ -358,7 +376,7 @@ done:
|
|||
int res;
|
||||
int r = thrd_join(bar_thread, &res);
|
||||
if (r != 0)
|
||||
LOG_ERRNO_P("failed to join bar thread", r);
|
||||
LOG_ERRNO_P(r, "failed to join bar thread");
|
||||
|
||||
bar->destroy(bar);
|
||||
close(abort_fd);
|
||||
|
|
95
meson.build
95
meson.build
|
@ -1,20 +1,28 @@
|
|||
project('yambar', 'c',
|
||||
version: '1.4.0',
|
||||
version: '1.11.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.48.0',
|
||||
default_options: ['c_std=c11',
|
||||
meson_version: '>=0.60.0',
|
||||
default_options: ['c_std=c18',
|
||||
'warning_level=1',
|
||||
'werror=true',
|
||||
'b_ndebug=if-release'])
|
||||
|
||||
is_debug_build = get_option('buildtype').startswith('debug')
|
||||
plugs_as_libs = get_option('core-plugins-as-shared-libraries')
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
cc_flags = [
|
||||
'-Werror=all'
|
||||
]
|
||||
|
||||
if cc.has_function('memfd_create',
|
||||
args: ['-D_GNU_SOURCE=200809L'],
|
||||
prefix: '#include <sys/mman.h>')
|
||||
add_project_arguments('-DMEMFD_CREATE', language: 'c')
|
||||
endif
|
||||
|
||||
# Compute the relative path used by compiler invocations.
|
||||
source_root = meson.current_source_dir().split('/')
|
||||
build_root = meson.build_root().split('/')
|
||||
build_root = meson.global_build_root().split('/')
|
||||
relative_dir_parts = []
|
||||
i = 0
|
||||
in_prefix = true
|
||||
|
@ -42,7 +50,10 @@ endif
|
|||
|
||||
# Common dependencies
|
||||
dl = cc.find_library('dl')
|
||||
threads = dependency('threads')
|
||||
m = cc.find_library('m')
|
||||
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
|
||||
libepoll = dependency('epoll-shim', required: false)
|
||||
libinotify = dependency('libinotify', required: false)
|
||||
pixman = dependency('pixman-1')
|
||||
yaml = dependency('yaml-0.1')
|
||||
|
||||
|
@ -60,14 +71,14 @@ backend_x11 = xcb_aux.found() and xcb_cursor.found() and xcb_event.found() and \
|
|||
# Wayland dependencies
|
||||
wayland_client = dependency('wayland-client', required: get_option('backend-wayland'))
|
||||
wayland_cursor = dependency('wayland-cursor', required: get_option('backend-wayland'))
|
||||
wlroots = dependency('wlroots', required: get_option('backend-wayland'))
|
||||
backend_wayland = wayland_client.found() and wayland_cursor.found() and wlroots.found()
|
||||
backend_wayland = wayland_client.found() and wayland_cursor.found()
|
||||
|
||||
# "My" dependencies, fallback to subproject
|
||||
tllist = dependency('tllist', version: '>=1.0.0', fallback: ['tllist', 'tllist'])
|
||||
fcft = dependency('fcft', version: ['>=1.1.0', '<1.2.0'], fallback: ['fcft', 'fcft'])
|
||||
tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist')
|
||||
fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft')
|
||||
|
||||
add_project_arguments(
|
||||
cc_flags +
|
||||
['-D_GNU_SOURCE'] +
|
||||
(is_debug_build ? ['-D_DEBUG'] : []) +
|
||||
(backend_x11 ? ['-DENABLE_X11'] : []) +
|
||||
|
@ -84,30 +95,37 @@ if backend_x11
|
|||
c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [],
|
||||
pic: plugs_as_libs)
|
||||
|
||||
xcb_stuff = declare_dependency(link_with: xcb_stuff_lib)
|
||||
xcb_stuff = declare_dependency(
|
||||
link_with: xcb_stuff_lib,
|
||||
dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr,
|
||||
xcb_render, xcb_errors],
|
||||
)
|
||||
install_headers('xcb.h', subdir: 'yambar')
|
||||
endif
|
||||
|
||||
subdir('completions')
|
||||
subdir('doc')
|
||||
subdir('bar')
|
||||
subdir('decorations')
|
||||
subdir('particles')
|
||||
subdir('modules')
|
||||
subdir('doc')
|
||||
|
||||
env = find_program('env', native: true)
|
||||
generate_version_sh = files('generate-version.sh')
|
||||
version = custom_target(
|
||||
'generate_version',
|
||||
build_always_stale: true,
|
||||
output: 'version.h',
|
||||
command: [generate_version_sh, meson.project_version(), '@SOURCE_DIR@', '@OUTPUT@'])
|
||||
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
|
||||
|
||||
yambar = executable(
|
||||
'yambar',
|
||||
'char32.c', 'char32.h',
|
||||
'color.h',
|
||||
'config-verify.c', 'config-verify.h',
|
||||
'config.c', 'config.h',
|
||||
'decoration.h',
|
||||
'font-shaping.h',
|
||||
'log.c', 'log.h',
|
||||
'main.c',
|
||||
'module.c', 'module.h',
|
||||
|
@ -116,13 +134,16 @@ yambar = executable(
|
|||
'tag.c', 'tag.h',
|
||||
'yml.c', 'yml.h',
|
||||
version,
|
||||
dependencies: [bar, pixman, yaml, threads, dl, tllist, fcft] +
|
||||
dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] +
|
||||
decorations + particles + modules,
|
||||
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
|
||||
export_dynamic: true,
|
||||
install: true,
|
||||
install_rpath: '$ORIGIN/../' + get_option('libdir') + '/yambar')
|
||||
|
||||
install_data(
|
||||
'LICENSE', 'README.md',
|
||||
install_dir: join_paths(get_option('datadir'), 'doc', 'yambar'))
|
||||
install_data('yambar.desktop', install_dir: join_paths(get_option('datadir'), 'applications'))
|
||||
|
||||
subdir('test')
|
||||
|
@ -140,9 +161,43 @@ install_headers(
|
|||
'yml.h',
|
||||
subdir: 'yambar')
|
||||
|
||||
message('')
|
||||
message('Build type: @0@'.format(get_option('buildtype')))
|
||||
message('XCB backend: @0@'.format(backend_x11))
|
||||
message('Wayland backend: @0@'.format(backend_wayland))
|
||||
message('Core modules as plugins: @0@'.format(plugs_as_libs))
|
||||
message('')
|
||||
summary(
|
||||
{
|
||||
'Build type': get_option('buildtype'),
|
||||
'XCB backend': backend_x11,
|
||||
'Wayland backend': backend_wayland,
|
||||
'Core modules as plugins': plugs_as_libs,
|
||||
},
|
||||
bool_yn: true
|
||||
)
|
||||
|
||||
summary(
|
||||
{
|
||||
'ALSA': plugin_alsa_enabled,
|
||||
'Backlight': plugin_backlight_enabled,
|
||||
'Battery': plugin_battery_enabled,
|
||||
'Clock': plugin_clock_enabled,
|
||||
'CPU monitoring': plugin_cpu_enabled,
|
||||
'Disk I/O monitoring': plugin_disk_io_enabled,
|
||||
'dwl (dwm for Wayland)': plugin_dwl_enabled,
|
||||
'Foreign toplevel (window tracking for Wayland)': plugin_foreign_toplevel_enabled,
|
||||
'Memory monitoring': plugin_mem_enabled,
|
||||
'Music Player Daemon (MPD)': plugin_mpd_enabled,
|
||||
'Media Player Remote Interface Specificaion (MPRIS)': plugin_mpris_enabled,
|
||||
'i3+Sway': plugin_i3_enabled,
|
||||
'Label': plugin_label_enabled,
|
||||
'Network monitoring': plugin_network_enabled,
|
||||
'Pipewire': plugin_pipewire_enabled,
|
||||
'PulseAudio': plugin_pulse_enabled,
|
||||
'Removables monitoring': plugin_removables_enabled,
|
||||
'River': plugin_river_enabled,
|
||||
'Script': plugin_script_enabled,
|
||||
'Sway XKB keyboard': plugin_sway_xkb_enabled,
|
||||
'Niri language': plugin_niri_language_enabled,
|
||||
'Niri workspaces': plugin_niri_workspaces_enabled,
|
||||
'XKB keyboard (for X11)': plugin_xkb_enabled,
|
||||
'XWindow (window tracking for X11)': plugin_xwindow_enabled,
|
||||
},
|
||||
section: 'Optional modules',
|
||||
bool_yn: true
|
||||
)
|
||||
|
|
|
@ -5,3 +5,52 @@ option(
|
|||
option(
|
||||
'core-plugins-as-shared-libraries', type: 'boolean', value: false,
|
||||
description: 'Compiles modules, particles and decorations as shared libraries, which are loaded on-demand')
|
||||
|
||||
option('plugin-alsa', type: 'feature', value: 'auto',
|
||||
description: 'ALSA support')
|
||||
option('plugin-backlight', type: 'feature', value: 'auto',
|
||||
description: 'Backlight support')
|
||||
option('plugin-battery', type: 'feature', value: 'auto',
|
||||
description: 'Battery support')
|
||||
option('plugin-clock', type: 'feature', value: 'auto',
|
||||
description: 'Clock support')
|
||||
option('plugin-cpu', type: 'feature', value: 'auto',
|
||||
description: 'CPU monitoring support')
|
||||
option('plugin-disk-io', type: 'feature', value: 'auto',
|
||||
description: 'Disk I/O support')
|
||||
option('plugin-dwl', type: 'feature', value: 'auto',
|
||||
description: 'dwl (dwm for wayland) support')
|
||||
option('plugin-foreign-toplevel', type: 'feature', value: 'auto',
|
||||
description: 'Foreign toplevel (window tracking for Wayland) support')
|
||||
option('plugin-mem', type: 'feature', value: 'auto',
|
||||
description: 'Memory monitoring support')
|
||||
option('plugin-mpd', type: 'feature', value: 'auto',
|
||||
description: 'Music Player Daemon (MPD) support')
|
||||
option('plugin-mpris', type: 'feature', value: 'enabled',
|
||||
description: 'Media Player Remote Interface Specificaion (MPRIS) support')
|
||||
option('plugin-i3', type: 'feature', value: 'auto',
|
||||
description: 'i3+Sway support')
|
||||
option('plugin-label', type: 'feature', value: 'auto',
|
||||
description: 'Label support')
|
||||
option('plugin-network', type: 'feature', value: 'auto',
|
||||
description: 'Network monitoring support')
|
||||
option('plugin-pipewire', type: 'feature', value: 'auto',
|
||||
description: 'Pipewire support')
|
||||
option('plugin-pulse', type: 'feature', value: 'auto',
|
||||
description: 'PulseAudio support')
|
||||
option('plugin-removables', type: 'feature', value: 'auto',
|
||||
description: 'Removables (USB sticks, CD-ROM etc) monitoring support')
|
||||
option('plugin-river', type: 'feature', value: 'auto',
|
||||
description: 'River support')
|
||||
option('plugin-script', type: 'feature', value: 'auto',
|
||||
description: 'Script support')
|
||||
option('plugin-sway-xkb', type: 'feature', value: 'auto',
|
||||
description: 'keyboard support for Sway')
|
||||
option('plugin-niri-language', type: 'feature', value: 'auto',
|
||||
description: 'language support for Niri')
|
||||
option('plugin-niri-workspaces', type: 'feature', value: 'auto',
|
||||
description: 'workspaces support for Niri')
|
||||
option('plugin-xkb', type: 'feature', value: 'auto',
|
||||
description: 'keyboard support for X11')
|
||||
option('plugin-xwindow', type: 'feature', value: 'auto',
|
||||
description: 'XWindow (window tracking for X11) support')
|
||||
|
|
2
module.c
2
module.c
|
@ -1,6 +1,6 @@
|
|||
#include "module.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct module *
|
||||
|
|
14
module.h
14
module.h
|
@ -26,6 +26,8 @@ struct module {
|
|||
/* refresh_in() should schedule a module content refresh after the
|
||||
* specified number of milliseconds */
|
||||
bool (*refresh_in)(struct module *mod, long milli_seconds);
|
||||
|
||||
const char *(*description)(const struct module *mod);
|
||||
};
|
||||
|
||||
struct module *module_common_new(void);
|
||||
|
@ -33,9 +35,9 @@ void module_default_destroy(struct module *mod);
|
|||
struct exposable *module_begin_expose(struct module *mod);
|
||||
|
||||
/* List of attributes *all* modules implement */
|
||||
#define MODULE_COMMON_ATTRS \
|
||||
{"content", true, &conf_verify_particle}, \
|
||||
{"anchors", false, NULL}, \
|
||||
{"font", false, &conf_verify_font}, \
|
||||
{"foreground", false, &conf_verify_color}, \
|
||||
{NULL, false, NULL}
|
||||
#define MODULE_COMMON_ATTRS \
|
||||
{"content", true, &conf_verify_particle}, {"anchors", false, NULL}, {"font", false, &conf_verify_font}, \
|
||||
{"foreground", false, &conf_verify_color}, \
|
||||
{ \
|
||||
NULL, false, NULL \
|
||||
}
|
||||
|
|
621
modules/alsa.c
621
modules/alsa.c
|
@ -1,5 +1,8 @@
|
|||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
|
@ -7,49 +10,149 @@
|
|||
|
||||
#define LOG_MODULE "alsa"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
char *card;
|
||||
char *mixer;
|
||||
struct particle *label;
|
||||
enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE };
|
||||
|
||||
tll(snd_mixer_selem_channel_id_t) channels;
|
||||
struct channel {
|
||||
snd_mixer_selem_channel_id_t id;
|
||||
enum channel_type type;
|
||||
char *name;
|
||||
|
||||
long vol_min;
|
||||
long vol_max;
|
||||
bool use_db;
|
||||
long vol_cur;
|
||||
long db_cur;
|
||||
bool muted;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
char *card;
|
||||
char *mixer;
|
||||
char *volume_name;
|
||||
char *muted_name;
|
||||
struct particle *label;
|
||||
|
||||
tll(struct channel) channels;
|
||||
|
||||
bool online;
|
||||
|
||||
bool has_playback_volume;
|
||||
long playback_vol_min;
|
||||
long playback_vol_max;
|
||||
|
||||
bool has_playback_db;
|
||||
long playback_db_min;
|
||||
long playback_db_max;
|
||||
|
||||
bool has_capture_volume;
|
||||
long capture_vol_min;
|
||||
long capture_vol_max;
|
||||
|
||||
long has_capture_db;
|
||||
long capture_db_min;
|
||||
long capture_db_max;
|
||||
|
||||
const struct channel *volume_chan;
|
||||
const struct channel *muted_chan;
|
||||
};
|
||||
|
||||
static void
|
||||
channel_free(struct channel *chan)
|
||||
{
|
||||
free(chan->name);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
tll_free(m->channels);
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
channel_free(&it->item);
|
||||
tll_remove(m->channels, it);
|
||||
}
|
||||
m->label->destroy(m->label);
|
||||
free(m->card);
|
||||
free(m->mixer);
|
||||
free(m->volume_name);
|
||||
free(m->muted_name);
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
static char desc[32];
|
||||
const struct private *m = mod->private;
|
||||
snprintf(desc, sizeof(desc), "alsa(%s)", m->card);
|
||||
return desc;
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const struct channel *volume_chan = m->volume_chan;
|
||||
const struct channel *muted_chan = m->muted_chan;
|
||||
|
||||
bool muted = muted_chan != NULL ? muted_chan->muted : false;
|
||||
long vol_min = 0, vol_max = 0, vol_cur = 0;
|
||||
long db_min = 0, db_max = 0, db_cur = 0;
|
||||
bool use_db = false;
|
||||
|
||||
if (volume_chan != NULL) {
|
||||
if (volume_chan->type == CHANNEL_PLAYBACK) {
|
||||
db_min = m->playback_db_min;
|
||||
db_max = m->playback_db_max;
|
||||
vol_min = m->playback_vol_min;
|
||||
vol_max = m->playback_vol_max;
|
||||
} else {
|
||||
db_min = m->capture_db_min;
|
||||
db_max = m->capture_db_max;
|
||||
vol_min = m->capture_vol_min;
|
||||
vol_max = m->capture_vol_max;
|
||||
}
|
||||
vol_cur = volume_chan->vol_cur;
|
||||
db_cur = volume_chan->db_cur;
|
||||
use_db = volume_chan->use_db;
|
||||
}
|
||||
|
||||
int percent;
|
||||
|
||||
if (use_db) {
|
||||
bool use_linear = db_max - db_min <= 24 * 100;
|
||||
if (use_linear) {
|
||||
percent = db_min - db_max > 0 ? round(100. * (db_cur - db_min) / (db_max - db_min)) : 0;
|
||||
} else {
|
||||
double normalized = pow(10, (double)(db_cur - db_max) / 6000.);
|
||||
if (db_min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
double min_norm = pow(10, (double)(db_min - db_max) / 6000.);
|
||||
normalized = (normalized - min_norm) / (1. - min_norm);
|
||||
}
|
||||
percent = round(100. * normalized);
|
||||
}
|
||||
} else {
|
||||
percent = vol_max - vol_min > 0 ? round(100. * (vol_cur - vol_min) / (vol_max - vol_min)) : 0;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_int_range(mod, "volume", m->vol_cur, m->vol_min, m->vol_max),
|
||||
tag_new_bool(mod, "muted", m->muted),
|
||||
tag_new_bool(mod, "online", m->online),
|
||||
tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max),
|
||||
tag_new_int_range(mod, "dB", db_cur, db_min, db_max),
|
||||
tag_new_int_range(mod, "percent", percent, 0, 100),
|
||||
tag_new_bool(mod, "muted", muted),
|
||||
},
|
||||
.count = 2,
|
||||
.count = 5,
|
||||
};
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
|
@ -64,135 +167,137 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
|
|||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
/* Get min/max volume levels */
|
||||
long min = 0, max = 0;
|
||||
int r = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_DBG("%s,%s: failed to get volume min/max (mixer is digital?)",
|
||||
m->card, m->mixer);
|
||||
}
|
||||
|
||||
/* Make sure min <= max */
|
||||
if (min > max) {
|
||||
LOG_WARN(
|
||||
"%s,%s: indicated minimum volume is greater than the maximum: "
|
||||
"%ld > %ld", m->card, m->mixer, min, max);
|
||||
min = max;
|
||||
}
|
||||
|
||||
long cur[tll_length(m->channels)];
|
||||
memset(cur, 0, sizeof(cur));
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
/* If volume level can be changed (i.e. this isn't just a switch;
|
||||
* e.g. a digital channel), get current level */
|
||||
if (max > 0) {
|
||||
tll_foreach(m->channels, it) {
|
||||
int r = snd_mixer_selem_get_playback_volume(
|
||||
elem, it->item, &cur[idx]);
|
||||
* e.g. a digital channel), get current channel levels */
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
struct channel *chan = &it->item;
|
||||
|
||||
const bool has_volume = chan->type == CHANNEL_PLAYBACK ? m->has_playback_volume : m->has_capture_volume;
|
||||
const bool has_db = chan->type == CHANNEL_PLAYBACK ? m->has_playback_db : m->has_capture_db;
|
||||
|
||||
if (!has_volume && !has_db)
|
||||
continue;
|
||||
|
||||
if (has_db) {
|
||||
chan->use_db = true;
|
||||
|
||||
const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_db_min : m->capture_db_min;
|
||||
const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_db_max : m->capture_db_max;
|
||||
assert(min <= max);
|
||||
|
||||
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_dB(elem, chan->id, &chan->db_cur)
|
||||
: snd_mixer_selem_get_capture_dB(elem, chan->id, &chan->db_cur);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_WARN("%s,%s: %s: failed to get current volume",
|
||||
m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(it->item));
|
||||
LOG_ERR("%s,%s: %s: failed to get current dB", m->card, m->mixer, chan->name);
|
||||
}
|
||||
|
||||
LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(it->item), cur[idx]);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
if (chan->db_cur < min) {
|
||||
LOG_WARN("%s,%s: %s: current dB is less than the indicated minimum: "
|
||||
"%ld < %ld",
|
||||
m->card, m->mixer, chan->name, chan->db_cur, min);
|
||||
chan->db_cur = min;
|
||||
}
|
||||
|
||||
int unmuted[tll_length(m->channels)];
|
||||
memset(unmuted, 0, sizeof(unmuted));
|
||||
if (chan->db_cur > max) {
|
||||
LOG_WARN("%s,%s: %s: current dB is greater than the indicated maximum: "
|
||||
"%ld > %ld",
|
||||
m->card, m->mixer, chan->name, chan->db_cur, max);
|
||||
chan->db_cur = max;
|
||||
}
|
||||
|
||||
/* Get muted state */
|
||||
idx = 0;
|
||||
tll_foreach(m->channels, it) {
|
||||
int r = snd_mixer_selem_get_playback_switch(
|
||||
elem, it->item, &unmuted[idx]);
|
||||
assert(chan->db_cur >= min);
|
||||
assert(chan->db_cur <= max);
|
||||
|
||||
LOG_DBG("%s,%s: %s: dB: %ld", m->card, m->mixer, chan->name, chan->db_cur);
|
||||
} else
|
||||
chan->use_db = false;
|
||||
|
||||
const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_min : m->capture_vol_min;
|
||||
const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_max : m->capture_vol_max;
|
||||
assert(min <= max);
|
||||
|
||||
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_volume(elem, chan->id, &chan->vol_cur)
|
||||
: snd_mixer_selem_get_capture_volume(elem, chan->id, &chan->vol_cur);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_WARN("%s,%s: %s: failed to get muted state",
|
||||
m->card, m->mixer, snd_mixer_selem_channel_name(it->item));
|
||||
LOG_ERR("%s,%s: %s: failed to get current volume", m->card, m->mixer, chan->name);
|
||||
}
|
||||
|
||||
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(it->item), !unmuted[idx]);
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
/* Warn if volume level is inconsistent across the channels */
|
||||
for (size_t i = 1; i < tll_length(m->channels); i++) {
|
||||
if (cur[i] != cur[i - 1]) {
|
||||
LOG_WARN("%s,%s: channel volume mismatch, using value from %s",
|
||||
m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(tll_front(m->channels)));
|
||||
break;
|
||||
if (chan->vol_cur < min) {
|
||||
LOG_WARN("%s,%s: %s: current volume is less than the indicated minimum: "
|
||||
"%ld < %ld",
|
||||
m->card, m->mixer, chan->name, chan->vol_cur, min);
|
||||
chan->vol_cur = min;
|
||||
}
|
||||
}
|
||||
|
||||
/* Warn if muted state is inconsistent across the channels */
|
||||
for (size_t i = 1; i < tll_length(m->channels); i++) {
|
||||
if (unmuted[i] != unmuted[i - 1]) {
|
||||
LOG_WARN("%s,%s: channel muted mismatch, using value from %s",
|
||||
m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(tll_front(m->channels)));
|
||||
break;
|
||||
if (chan->vol_cur > max) {
|
||||
LOG_WARN("%s,%s: %s: current volume is greater than the indicated maximum: "
|
||||
"%ld > %ld",
|
||||
m->card, m->mixer, chan->name, chan->vol_cur, max);
|
||||
chan->vol_cur = max;
|
||||
}
|
||||
|
||||
assert(chan->vol_cur >= min);
|
||||
assert(chan->vol_cur <= max);
|
||||
|
||||
LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer, chan->name, chan->vol_cur);
|
||||
}
|
||||
|
||||
/* Make sure min <= cur <= max */
|
||||
if (cur[0] < min) {
|
||||
LOG_WARN(
|
||||
"%s,%s: current volume is less than the indicated minimum: "
|
||||
"%ld < %ld", m->card, m->mixer, cur[0], min);
|
||||
cur[0] = min;
|
||||
/* Get channels’ muted state */
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
struct channel *chan = &it->item;
|
||||
|
||||
int unmuted;
|
||||
|
||||
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_switch(elem, chan->id, &unmuted)
|
||||
: snd_mixer_selem_get_capture_switch(elem, chan->id, &unmuted);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_WARN("%s,%s: %s: failed to get muted state", m->card, m->mixer, chan->name);
|
||||
unmuted = 1;
|
||||
}
|
||||
|
||||
chan->muted = !unmuted;
|
||||
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted);
|
||||
}
|
||||
|
||||
if (cur[0] > max) {
|
||||
LOG_WARN(
|
||||
"%s,%s: current volume is greater than the indicated maximum: "
|
||||
"%ld > %ld", m->card, m->mixer, cur[0], max);
|
||||
cur[0] = max;
|
||||
}
|
||||
m->online = true;
|
||||
|
||||
assert(cur[0] >= min);
|
||||
assert(cur[0] <= max);
|
||||
|
||||
LOG_DBG(
|
||||
"muted=%d, cur=%ld, min=%ld, max=%ld", !unmuted[0], cur[0], min, max);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
m->vol_min = min;
|
||||
m->vol_max = max;
|
||||
m->vol_cur = cur[0];
|
||||
m->muted = !unmuted[0];
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
mod->bar->refresh(mod->bar);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
enum run_state {
|
||||
RUN_ERROR,
|
||||
RUN_FAILED_CONNECT,
|
||||
RUN_DISCONNECTED,
|
||||
RUN_DONE,
|
||||
};
|
||||
|
||||
static enum run_state
|
||||
run_while_online(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
int ret = 1;
|
||||
enum run_state ret = RUN_ERROR;
|
||||
|
||||
/* Make sure we aren’t still tracking channels from previous connects */
|
||||
tll_free(m->channels);
|
||||
|
||||
snd_mixer_t *handle;
|
||||
if (snd_mixer_open(&handle, 0) != 0) {
|
||||
LOG_ERR("failed to open handle");
|
||||
return 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (snd_mixer_attach(handle, m->card) != 0 ||
|
||||
snd_mixer_selem_register(handle, NULL, NULL) != 0 ||
|
||||
snd_mixer_load(handle) != 0)
|
||||
{
|
||||
if (snd_mixer_attach(handle, m->card) != 0 || snd_mixer_selem_register(handle, NULL, NULL) != 0
|
||||
|| snd_mixer_load(handle) != 0) {
|
||||
LOG_ERR("failed to attach to card");
|
||||
ret = RUN_FAILED_CONNECT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
@ -201,37 +306,142 @@ run(struct module *mod)
|
|||
snd_mixer_selem_id_set_index(sid, 0);
|
||||
snd_mixer_selem_id_set_name(sid, m->mixer);
|
||||
|
||||
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
|
||||
snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid);
|
||||
if (elem == NULL) {
|
||||
LOG_ERR("failed to find mixer");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Get playback volume range */
|
||||
m->has_playback_volume = snd_mixer_selem_has_playback_volume(elem) > 0;
|
||||
if (m->has_playback_volume) {
|
||||
if (snd_mixer_selem_get_playback_volume_range(elem, &m->playback_vol_min, &m->playback_vol_max) < 0) {
|
||||
LOG_ERR("%s,%s: failed to get playback volume range", m->card, m->mixer);
|
||||
assert(m->playback_vol_min == 0);
|
||||
assert(m->playback_vol_max == 0);
|
||||
}
|
||||
|
||||
if (m->playback_vol_min > m->playback_vol_max) {
|
||||
LOG_WARN("%s,%s: indicated minimum playback volume is greater than the "
|
||||
"maximum: %ld > %ld",
|
||||
m->card, m->mixer, m->playback_vol_min, m->playback_vol_max);
|
||||
m->playback_vol_min = m->playback_vol_max;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_mixer_selem_get_playback_dB_range(elem, &m->playback_db_min, &m->playback_db_max) < 0) {
|
||||
LOG_WARN("%s,%s: failed to get playback dB range, "
|
||||
"will use raw volume values instead",
|
||||
m->card, m->mixer);
|
||||
m->has_playback_db = false;
|
||||
} else
|
||||
m->has_playback_db = true;
|
||||
|
||||
/* Get capture volume range */
|
||||
m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0;
|
||||
if (m->has_capture_volume) {
|
||||
if (snd_mixer_selem_get_capture_volume_range(elem, &m->capture_vol_min, &m->capture_vol_max) < 0) {
|
||||
LOG_ERR("%s,%s: failed to get capture volume range", m->card, m->mixer);
|
||||
assert(m->capture_vol_min == 0);
|
||||
assert(m->capture_vol_max == 0);
|
||||
}
|
||||
|
||||
if (m->capture_vol_min > m->capture_vol_max) {
|
||||
LOG_WARN("%s,%s: indicated minimum capture volume is greater than the "
|
||||
"maximum: %ld > %ld",
|
||||
m->card, m->mixer, m->capture_vol_min, m->capture_vol_max);
|
||||
m->capture_vol_min = m->capture_vol_max;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_mixer_selem_get_capture_dB_range(elem, &m->capture_db_min, &m->capture_db_max) < 0) {
|
||||
LOG_WARN("%s,%s: failed to get capture dB range, "
|
||||
"will use raw volume values instead",
|
||||
m->card, m->mixer);
|
||||
m->has_capture_db = false;
|
||||
} else
|
||||
m->has_capture_db = true;
|
||||
|
||||
/* Get available channels */
|
||||
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) {
|
||||
if (snd_mixer_selem_has_playback_channel(elem, i)) {
|
||||
tll_push_back(m->channels, i);
|
||||
bool is_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1;
|
||||
bool is_capture = snd_mixer_selem_has_capture_channel(elem, i) == 1;
|
||||
|
||||
if (is_playback || is_capture) {
|
||||
struct channel chan = {
|
||||
.id = i,
|
||||
.type = is_playback ? CHANNEL_PLAYBACK : CHANNEL_CAPTURE,
|
||||
.name = strdup(snd_mixer_selem_channel_name(i)),
|
||||
};
|
||||
tll_push_back(m->channels, chan);
|
||||
}
|
||||
}
|
||||
|
||||
if (tll_length(m->channels) == 0) {
|
||||
LOG_ERR("%s,%s: no channels", m->card, m->mixer);
|
||||
goto err;
|
||||
}
|
||||
|
||||
char channels_str[1024];
|
||||
int channels_idx = 0;
|
||||
tll_foreach(m->channels, it) {
|
||||
channels_idx += snprintf(
|
||||
&channels_str[channels_idx], sizeof(channels_str) - channels_idx,
|
||||
channels_idx == 0 ? "%s" : ", %s",
|
||||
snd_mixer_selem_channel_name(it->item));
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
const struct channel *chan = &it->item;
|
||||
|
||||
channels_idx += snprintf(&channels_str[channels_idx], sizeof(channels_str) - channels_idx,
|
||||
channels_idx == 0 ? "%s (%s)" : ", %s (%s)", chan->name,
|
||||
chan->type == CHANNEL_PLAYBACK ? "🔊" : "🎤");
|
||||
assert(channels_idx <= sizeof(channels_str));
|
||||
}
|
||||
|
||||
LOG_INFO("%s,%s: channels: %s", m->card, m->mixer, channels_str);
|
||||
|
||||
/* Verify volume/muted channel names are valid and exists */
|
||||
bool volume_channel_is_valid = m->volume_name == NULL;
|
||||
bool muted_channel_is_valid = m->muted_name == NULL;
|
||||
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
const struct channel *chan = &it->item;
|
||||
if (m->volume_name != NULL && strcmp(chan->name, m->volume_name) == 0) {
|
||||
m->volume_chan = chan;
|
||||
volume_channel_is_valid = true;
|
||||
}
|
||||
if (m->muted_name != NULL && strcmp(chan->name, m->muted_name) == 0) {
|
||||
m->muted_chan = chan;
|
||||
muted_channel_is_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m->volume_name == NULL)
|
||||
m->volume_chan = &tll_front(m->channels);
|
||||
if (m->muted_name == NULL)
|
||||
m->muted_chan = &tll_front(m->channels);
|
||||
|
||||
if (!volume_channel_is_valid) {
|
||||
assert(m->volume_name != NULL);
|
||||
LOG_ERR("volume: invalid channel name: %s", m->volume_name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!muted_channel_is_valid) {
|
||||
assert(m->muted_name != NULL);
|
||||
LOG_ERR("muted: invalid channel name: %s", m->muted_name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Initial state */
|
||||
update_state(mod, elem);
|
||||
|
||||
LOG_INFO("%s,%s: volume min=%ld, max=%ld, current=%ld%s",
|
||||
m->card, m->mixer, m->vol_min, m->vol_max, m->vol_cur,
|
||||
m->muted ? ", muted" : "");
|
||||
LOG_INFO(
|
||||
"%s,%s: %s range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)", m->card, m->mixer,
|
||||
m->volume_chan->use_db ? "dB" : "volume",
|
||||
(m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_min : m->playback_vol_min)
|
||||
: (m->volume_chan->use_db ? m->capture_db_min : m->capture_vol_min)),
|
||||
(m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_max : m->playback_vol_max)
|
||||
: (m->volume_chan->use_db ? m->capture_db_max : m->capture_vol_max)),
|
||||
m->volume_chan->use_db ? m->volume_chan->db_cur : m->volume_chan->vol_cur,
|
||||
m->muted_chan->muted ? " (muted)" : "", m->volume_chan->name, m->muted_chan->name);
|
||||
|
||||
mod->bar->refresh(mod->bar);
|
||||
|
||||
|
@ -244,42 +454,188 @@ run(struct module *mod)
|
|||
fds[0] = (struct pollfd){.fd = mod->abort_fd, .events = POLLIN};
|
||||
snd_mixer_poll_descriptors(handle, &fds[1], fd_count);
|
||||
|
||||
poll(fds, fd_count + 1, -1);
|
||||
int r = poll(fds, fd_count + 1, -1);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
/* Don't know if this can happen */
|
||||
LOG_ERR("disconnected from alsa");
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = RUN_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < fd_count; i++) {
|
||||
if (fds[1 + i].revents & (POLLHUP | POLLERR | POLLNVAL)) {
|
||||
LOG_ERR("disconnected from alsa");
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
m->online = false;
|
||||
mtx_unlock(&mod->lock);
|
||||
mod->bar->refresh(mod->bar);
|
||||
|
||||
ret = RUN_DISCONNECTED;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
snd_mixer_handle_events(handle);
|
||||
update_state(mod, elem);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
snd_mixer_close(handle);
|
||||
snd_config_update_free_global();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
int ret = 1;
|
||||
|
||||
int ifd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
if (ifd < 0) {
|
||||
LOG_ERRNO("failed to inotify");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wd = inotify_add_watch(ifd, "/dev/snd", IN_CREATE);
|
||||
if (wd < 0) {
|
||||
LOG_ERRNO("failed to create inotify watcher for /dev/snd");
|
||||
close(ifd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
enum run_state state = run_while_online(mod);
|
||||
|
||||
switch (state) {
|
||||
case RUN_DONE:
|
||||
ret = 0;
|
||||
goto out;
|
||||
|
||||
case RUN_ERROR:
|
||||
ret = 1;
|
||||
goto out;
|
||||
|
||||
case RUN_FAILED_CONNECT:
|
||||
break;
|
||||
|
||||
case RUN_DISCONNECTED:
|
||||
/*
|
||||
* We’ve been connected - drain the watcher
|
||||
*
|
||||
* We don’t want old, un-releated events (for other
|
||||
* soundcards, for example) to trigger a storm of
|
||||
* re-connect attempts.
|
||||
*/
|
||||
while (true) {
|
||||
uint8_t buf[1024];
|
||||
ssize_t amount = read(ifd, buf, sizeof(buf));
|
||||
if (amount < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
LOG_ERRNO("failed to drain inotify watcher");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (amount == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bool have_create_event = false;
|
||||
|
||||
while (!have_create_event) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = ifd, .events = POLLIN}};
|
||||
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fds[0].revents & (POLLIN | POLLHUP)) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_ERR("inotify socket closed");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
assert(fds[1].revents & POLLIN);
|
||||
|
||||
while (true) {
|
||||
char buf[1024];
|
||||
ssize_t len = read(ifd, buf, sizeof(buf));
|
||||
|
||||
if (len < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
LOG_ERRNO("failed to read inotify events");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (len == 0)
|
||||
break;
|
||||
|
||||
/* Consume inotify data */
|
||||
for (const char *ptr = buf; ptr < buf + len;) {
|
||||
const struct inotify_event *e = (const struct inotify_event *)ptr;
|
||||
|
||||
if (e->mask & IN_CREATE) {
|
||||
LOG_DBG("inotify: CREATED: /dev/snd/%.*s", e->len, e->name);
|
||||
have_create_event = true;
|
||||
}
|
||||
|
||||
ptr += sizeof(*e) + e->len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (wd >= 0)
|
||||
inotify_rm_watch(ifd, wd);
|
||||
if (ifd >= 0)
|
||||
close(ifd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
alsa_new(const char *card, const char *mixer, struct particle *label)
|
||||
alsa_new(const char *card, const char *mixer, const char *volume_channel_name, const char *muted_channel_name,
|
||||
struct particle *label)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->label = label;
|
||||
priv->card = strdup(card);
|
||||
priv->mixer = strdup(mixer);
|
||||
priv->volume_name = volume_channel_name != NULL ? strdup(volume_channel_name) : NULL;
|
||||
priv->muted_name = muted_channel_name != NULL ? strdup(muted_channel_name) : NULL;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = priv;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
@ -288,12 +644,13 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
{
|
||||
const struct yml_node *card = yml_get_value(node, "card");
|
||||
const struct yml_node *mixer = yml_get_value(node, "mixer");
|
||||
const struct yml_node *volume = yml_get_value(node, "volume");
|
||||
const struct yml_node *muted = yml_get_value(node, "muted");
|
||||
const struct yml_node *content = yml_get_value(node, "content");
|
||||
|
||||
return alsa_new(
|
||||
yml_value_as_string(card),
|
||||
yml_value_as_string(mixer),
|
||||
conf_to_particle(content, inherited));
|
||||
return alsa_new(yml_value_as_string(card), yml_value_as_string(mixer),
|
||||
volume != NULL ? yml_value_as_string(volume) : NULL,
|
||||
muted != NULL ? yml_value_as_string(muted) : NULL, conf_to_particle(content, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -302,6 +659,8 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
static const struct attr_info attrs[] = {
|
||||
{"card", true, &conf_verify_string},
|
||||
{"mixer", true, &conf_verify_string},
|
||||
{"volume", false, &conf_verify_string},
|
||||
{"muted", false, &conf_verify_string},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <libudev.h>
|
||||
|
||||
#define LOG_MODULE "backlight"
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
|
||||
char *device;
|
||||
|
@ -37,6 +40,12 @@ destroy(struct module *mod)
|
|||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "backlight";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
|
@ -46,7 +55,7 @@ content(struct module *mod)
|
|||
|
||||
const long current = m->current_brightness;
|
||||
const long max = m->max_brightness;
|
||||
const long percent = max > 0 ? 100 * current / max : 0;
|
||||
const long percent = max > 0 ? round(100. * current / max) : 0;
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
|
@ -103,13 +112,13 @@ readint_from_fd(int fd)
|
|||
static int
|
||||
initialize(struct private *m)
|
||||
{
|
||||
int backlight_fd = open("/sys/class/backlight", O_RDONLY);
|
||||
int backlight_fd = open("/sys/class/backlight", O_RDONLY | O_CLOEXEC);
|
||||
if (backlight_fd == -1) {
|
||||
LOG_ERRNO("/sys/class/backlight");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY);
|
||||
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY | O_CLOEXEC);
|
||||
close(backlight_fd);
|
||||
|
||||
if (base_dir_fd == -1) {
|
||||
|
@ -117,7 +126,7 @@ initialize(struct private *m)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY);
|
||||
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY | O_CLOEXEC);
|
||||
if (max_fd == -1) {
|
||||
LOG_ERRNO("/sys/class/backlight/%s/max_brightness", m->device);
|
||||
close(base_dir_fd);
|
||||
|
@ -127,7 +136,7 @@ initialize(struct private *m)
|
|||
m->max_brightness = readint_from_fd(max_fd);
|
||||
close(max_fd);
|
||||
|
||||
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY);
|
||||
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY | O_CLOEXEC);
|
||||
close(base_dir_fd);
|
||||
|
||||
if (current_fd == -1) {
|
||||
|
@ -137,8 +146,7 @@ initialize(struct private *m)
|
|||
|
||||
m->current_brightness = readint_from_fd(current_fd);
|
||||
|
||||
LOG_INFO("%s: brightness: %ld (max: %ld)", m->device, m->current_brightness,
|
||||
m->max_brightness);
|
||||
LOG_INFO("%s: brightness: %ld (max: %ld)", m->device, m->current_brightness, m->max_brightness);
|
||||
|
||||
return current_fd;
|
||||
}
|
||||
|
@ -171,20 +179,31 @@ run(struct module *mod)
|
|||
|
||||
bar->refresh(bar);
|
||||
|
||||
int ret = 1;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = udev_monitor_get_fd(mon), .events = POLLIN},
|
||||
};
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
struct udev_device *dev = udev_monitor_receive_device(mon);
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
if (dev == NULL)
|
||||
continue;
|
||||
|
||||
bool is_us = strcmp(sysname, m->device) == 0;
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
bool is_us = sysname != NULL && strcmp(sysname, m->device) == 0;
|
||||
udev_device_unref(dev);
|
||||
|
||||
if (!is_us)
|
||||
|
@ -200,7 +219,7 @@ run(struct module *mod)
|
|||
udev_unref(udev);
|
||||
|
||||
close(current_fd);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
|
@ -215,6 +234,7 @@ backlight_new(const char *device, struct particle *label)
|
|||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
@ -224,8 +244,7 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *name = yml_get_value(node, "name");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return backlight_new(
|
||||
yml_value_as_string(name), conf_to_particle(c, inherited));
|
||||
return backlight_new(yml_value_as_string(name), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
|
@ -1,41 +1,120 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <libudev.h>
|
||||
#include <tllist.h>
|
||||
|
||||
#define LOG_MODULE "battery"
|
||||
#include "../log.h"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
enum state { STATE_FULL, STATE_CHARGING, STATE_DISCHARGING };
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
struct private {
|
||||
static const long min_poll_interval = 250;
|
||||
static const long default_poll_interval = 60 * 1000;
|
||||
static const long one_sec_in_ns = 1000000000;
|
||||
|
||||
enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING, STATE_UNKNOWN };
|
||||
|
||||
struct current_state {
|
||||
long ema;
|
||||
long current;
|
||||
struct timespec time;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
|
||||
int poll_interval;
|
||||
long poll_interval;
|
||||
int battery_scale;
|
||||
long smoothing_scale;
|
||||
char *battery;
|
||||
char *manufacturer;
|
||||
char *model;
|
||||
long energy_full_design;
|
||||
long energy_full;
|
||||
long charge_full_design;
|
||||
long charge_full;
|
||||
|
||||
enum state state;
|
||||
long capacity;
|
||||
long energy;
|
||||
long power;
|
||||
long charge;
|
||||
struct current_state ema_current;
|
||||
long time_to_empty;
|
||||
long time_to_full;
|
||||
};
|
||||
|
||||
static int64_t
|
||||
difftimespec_ns(const struct timespec after, const struct timespec before)
|
||||
{
|
||||
return ((int64_t)after.tv_sec - (int64_t)before.tv_sec) * (int64_t)one_sec_in_ns
|
||||
+ ((int64_t)after.tv_nsec - (int64_t)before.tv_nsec);
|
||||
}
|
||||
|
||||
// Linear Exponential Moving Average (unevenly spaced time series)
|
||||
// http://www.eckner.com/papers/Algorithms%20for%20Unevenly%20Spaced%20Time%20Series.pdf
|
||||
// Adapted from: https://github.com/andreas50/utsAlgorithms/blob/master/ema.c
|
||||
static void
|
||||
ema_linear(struct current_state *state, struct current_state curr, long tau)
|
||||
{
|
||||
double w, w2, tmp;
|
||||
|
||||
if (state->current == -1) {
|
||||
*state = curr;
|
||||
return;
|
||||
}
|
||||
|
||||
long time = difftimespec_ns(curr.time, state->time);
|
||||
tmp = time / (double)tau;
|
||||
w = exp(-tmp);
|
||||
if (tmp > 1e-6) {
|
||||
w2 = (1 - w) / tmp;
|
||||
} else {
|
||||
// Use taylor expansion for numerical stability
|
||||
w2 = 1 - tmp / 2 + tmp * tmp / 6 - tmp * tmp * tmp / 24;
|
||||
}
|
||||
|
||||
double ema = state->ema * w + curr.current * (1 - w2) + state->current * (w2 - w);
|
||||
|
||||
state->ema = ema;
|
||||
state->current = curr.current;
|
||||
state->time = curr.time;
|
||||
|
||||
LOG_DBG("ema current: %ld", (long)ema);
|
||||
}
|
||||
|
||||
static void
|
||||
timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res)
|
||||
{
|
||||
|
||||
res->tv_sec = a->tv_sec - b->tv_sec;
|
||||
res->tv_nsec = a->tv_nsec - b->tv_nsec;
|
||||
|
||||
/* tv_nsec may be negative */
|
||||
if (res->tv_nsec < 0) {
|
||||
res->tv_sec--;
|
||||
res->tv_nsec += one_sec_in_ns;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
|
@ -50,6 +129,15 @@ destroy(struct module *mod)
|
|||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
static char desc[32];
|
||||
const struct private *m = mod->private;
|
||||
snprintf(desc, sizeof(desc), "bat(%s)", m->battery);
|
||||
return desc;
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
|
@ -57,23 +145,50 @@ content(struct module *mod)
|
|||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
assert(m->state == STATE_FULL ||
|
||||
m->state == STATE_CHARGING ||
|
||||
m->state == STATE_DISCHARGING);
|
||||
assert(m->state == STATE_FULL || m->state == STATE_NOTCHARGING || m->state == STATE_CHARGING
|
||||
|| m->state == STATE_DISCHARGING || m->state == STATE_UNKNOWN);
|
||||
|
||||
unsigned long energy = m->state == STATE_CHARGING
|
||||
? m->energy_full - m->energy : m->energy;
|
||||
unsigned long hours;
|
||||
unsigned long minutes;
|
||||
|
||||
double hours_as_float;
|
||||
if (m->state == STATE_FULL)
|
||||
hours_as_float = 0.0;
|
||||
else if (m->power > 0)
|
||||
hours_as_float = (double)energy / m->power;
|
||||
else
|
||||
hours_as_float = 99.0;
|
||||
if (m->time_to_empty > 0) {
|
||||
minutes = m->time_to_empty / 60;
|
||||
hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
} else if (m->time_to_full > 0) {
|
||||
minutes = m->time_to_full / 60;
|
||||
hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
} else if (m->energy_full >= 0 && m->charge && m->power >= 0) {
|
||||
unsigned long energy = m->state == STATE_CHARGING ? m->energy_full - m->energy : m->energy;
|
||||
|
||||
unsigned long hours = hours_as_float;
|
||||
unsigned long minutes = (hours_as_float - (double)hours) * 60;
|
||||
double hours_as_float;
|
||||
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
|
||||
hours_as_float = 0.0;
|
||||
else if (m->power > 0)
|
||||
hours_as_float = (double)energy / m->power;
|
||||
else
|
||||
hours_as_float = 99.0;
|
||||
|
||||
hours = hours_as_float;
|
||||
minutes = (hours_as_float - (double)hours) * 60;
|
||||
} else if (m->charge_full >= 0 && m->charge >= 0 && m->ema_current.current >= 0) {
|
||||
unsigned long charge = m->state == STATE_CHARGING ? m->charge_full - m->charge : m->charge;
|
||||
|
||||
double hours_as_float;
|
||||
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
|
||||
hours_as_float = 0.0;
|
||||
else if (m->ema_current.current > 0)
|
||||
hours_as_float = (double)charge / m->ema_current.current;
|
||||
else
|
||||
hours_as_float = 99.0;
|
||||
|
||||
hours = hours_as_float;
|
||||
minutes = (hours_as_float - (double)hours) * 60;
|
||||
} else {
|
||||
hours = 99;
|
||||
minutes = 0;
|
||||
}
|
||||
|
||||
char estimate[64];
|
||||
snprintf(estimate, sizeof(estimate), "%02lu:%02lu", hours, minutes);
|
||||
|
@ -85,6 +200,7 @@ content(struct module *mod)
|
|||
tag_new_string(mod, "model", m->model),
|
||||
tag_new_string(mod, "state",
|
||||
m->state == STATE_FULL ? "full" :
|
||||
m->state == STATE_NOTCHARGING ? "not charging" :
|
||||
m->state == STATE_CHARGING ? "charging" :
|
||||
m->state == STATE_DISCHARGING ? "discharging" :
|
||||
"unknown"),
|
||||
|
@ -103,20 +219,18 @@ content(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
readline_from_fd(int fd)
|
||||
readline_from_fd(int fd, size_t sz, char buf[static sz])
|
||||
{
|
||||
static char buf[4096];
|
||||
|
||||
ssize_t sz = read(fd, buf, sizeof(buf) - 1);
|
||||
ssize_t bytes = read(fd, buf, sz - 1);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
if (sz < 0) {
|
||||
if (bytes < 0) {
|
||||
LOG_WARN("failed to read from FD=%d", fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf[sz] = '\0';
|
||||
for (ssize_t i = sz - 1; i >= 0 && buf[i] == '\n'; sz--)
|
||||
buf[bytes] = '\0';
|
||||
for (ssize_t i = bytes - 1; i >= 0 && buf[i] == '\n'; bytes--)
|
||||
buf[i] = '\0';
|
||||
|
||||
return buf;
|
||||
|
@ -125,7 +239,8 @@ readline_from_fd(int fd)
|
|||
static long
|
||||
readint_from_fd(int fd)
|
||||
{
|
||||
const char *s = readline_from_fd(fd);
|
||||
char buf[512];
|
||||
const char *s = readline_from_fd(fd, sizeof(buf), buf);
|
||||
if (s == NULL)
|
||||
return 0;
|
||||
|
||||
|
@ -139,108 +254,226 @@ readint_from_fd(int fd)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
static bool
|
||||
initialize(struct private *m)
|
||||
{
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
|
||||
if (pw_fd == -1) {
|
||||
char line_buf[512];
|
||||
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
|
||||
if (pw_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply");
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
|
||||
close(pw_fd);
|
||||
|
||||
if (base_dir_fd == -1) {
|
||||
LOG_ERRNO("%s", m->battery);
|
||||
return -1;
|
||||
if (base_dir_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s", m->battery);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/manufacturer", m->battery);
|
||||
goto err;
|
||||
LOG_WARN("/sys/class/power_supply/%s/manufacturer: %s", m->battery, strerror(errno));
|
||||
m->manufacturer = NULL;
|
||||
} else {
|
||||
m->manufacturer = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf));
|
||||
close(fd);
|
||||
}
|
||||
|
||||
m->manufacturer = strdup(readline_from_fd(fd));
|
||||
close(fd);
|
||||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "model_name", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "model_name", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/model_name", m->battery);
|
||||
goto err;
|
||||
LOG_WARN("/sys/class/power_supply/%s/model_name: %s", m->battery, strerror(errno));
|
||||
m->model = NULL;
|
||||
} else {
|
||||
m->model = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf));
|
||||
close(fd);
|
||||
}
|
||||
|
||||
m->model = strdup(readline_from_fd(fd));
|
||||
close(fd);
|
||||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery);
|
||||
goto err;
|
||||
if (faccessat(base_dir_fd, "energy_full_design", O_RDONLY, 0) == 0
|
||||
&& faccessat(base_dir_fd, "energy_full", O_RDONLY, 0) == 0) {
|
||||
{
|
||||
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery);
|
||||
goto err;
|
||||
}
|
||||
|
||||
m->energy_full_design = readint_from_fd(fd);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
m->energy_full_design = readint_from_fd(fd);
|
||||
close(fd);
|
||||
{
|
||||
int fd = openat(base_dir_fd, "energy_full", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery);
|
||||
goto err;
|
||||
}
|
||||
|
||||
m->energy_full = readint_from_fd(fd);
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
m->energy_full = m->energy_full_design = -1;
|
||||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "energy_full", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery);
|
||||
goto err;
|
||||
if (faccessat(base_dir_fd, "charge_full_design", O_RDONLY, 0) == 0
|
||||
&& faccessat(base_dir_fd, "charge_full", O_RDONLY, 0) == 0) {
|
||||
{
|
||||
int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery);
|
||||
goto err;
|
||||
}
|
||||
|
||||
m->charge_full_design = readint_from_fd(fd) / m->battery_scale;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
m->energy_full = readint_from_fd(fd);
|
||||
close(fd);
|
||||
{
|
||||
int fd = openat(base_dir_fd, "charge_full", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery);
|
||||
goto err;
|
||||
}
|
||||
|
||||
m->charge_full = readint_from_fd(fd) / m->battery_scale;
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
m->charge_full = m->charge_full_design = -1;
|
||||
}
|
||||
|
||||
return base_dir_fd;
|
||||
close(base_dir_fd);
|
||||
return true;
|
||||
|
||||
err:
|
||||
close(base_dir_fd);
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
update_status(struct module *mod, int capacity_fd, int energy_fd, int power_fd,
|
||||
int status_fd)
|
||||
static bool
|
||||
update_status(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
long capacity = readint_from_fd(capacity_fd);
|
||||
long energy = readint_from_fd(energy_fd);
|
||||
long power = readint_from_fd(power_fd);
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
|
||||
if (pw_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply");
|
||||
return false;
|
||||
}
|
||||
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
|
||||
close(pw_fd);
|
||||
|
||||
if (base_dir_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s", m->battery);
|
||||
return false;
|
||||
}
|
||||
|
||||
int status_fd = openat(base_dir_fd, "status", O_RDONLY | O_CLOEXEC);
|
||||
if (status_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery);
|
||||
close(base_dir_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY | O_CLOEXEC);
|
||||
if (capacity_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery);
|
||||
close(status_fd);
|
||||
close(base_dir_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY | O_CLOEXEC);
|
||||
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY | O_CLOEXEC);
|
||||
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY | O_CLOEXEC);
|
||||
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY | O_CLOEXEC);
|
||||
int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY | O_CLOEXEC);
|
||||
int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY | O_CLOEXEC);
|
||||
|
||||
long capacity = readint_from_fd(capacity_fd);
|
||||
long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1;
|
||||
long power = power_fd >= 0 ? readint_from_fd(power_fd) : -1;
|
||||
long charge = charge_fd >= 0 ? readint_from_fd(charge_fd) : -1;
|
||||
long current = current_fd >= 0 ? readint_from_fd(current_fd) : -1;
|
||||
long time_to_empty = time_to_empty_fd >= 0 ? readint_from_fd(time_to_empty_fd) : -1;
|
||||
long time_to_full = time_to_full_fd >= 0 ? readint_from_fd(time_to_full_fd) : -1;
|
||||
|
||||
if (charge >= -1) {
|
||||
charge /= m->battery_scale;
|
||||
}
|
||||
|
||||
char buf[512];
|
||||
const char *status = readline_from_fd(status_fd, sizeof(buf), buf);
|
||||
|
||||
if (status_fd >= 0)
|
||||
close(status_fd);
|
||||
if (capacity_fd >= 0)
|
||||
close(capacity_fd);
|
||||
if (energy_fd >= 0)
|
||||
close(energy_fd);
|
||||
if (power_fd >= 0)
|
||||
close(power_fd);
|
||||
if (charge_fd >= 0)
|
||||
close(charge_fd);
|
||||
if (current_fd >= 0)
|
||||
close(current_fd);
|
||||
if (time_to_empty_fd >= 0)
|
||||
close(time_to_empty_fd);
|
||||
if (time_to_full_fd >= 0)
|
||||
close(time_to_full_fd);
|
||||
if (base_dir_fd >= 0)
|
||||
close(base_dir_fd);
|
||||
|
||||
const char *status = readline_from_fd(status_fd);
|
||||
enum state state;
|
||||
|
||||
if (strcmp(status, "Full") == 0)
|
||||
if (status == NULL) {
|
||||
LOG_WARN("failed to read battery state");
|
||||
state = STATE_UNKNOWN;
|
||||
} else if (strcmp(status, "Full") == 0)
|
||||
state = STATE_FULL;
|
||||
else if (strcmp(status, "Not charging") == 0)
|
||||
state = STATE_NOTCHARGING;
|
||||
else if (strcmp(status, "Charging") == 0)
|
||||
state = STATE_CHARGING;
|
||||
else if (strcmp(status, "Discharging") == 0)
|
||||
state = STATE_DISCHARGING;
|
||||
else if (strcmp(status, "Unknown") == 0)
|
||||
state = STATE_DISCHARGING;
|
||||
state = STATE_UNKNOWN;
|
||||
else {
|
||||
LOG_ERR("unrecognized battery state: %s", status);
|
||||
state = STATE_DISCHARGING;
|
||||
state = STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
LOG_DBG("capacity: %ld, energy: %ld, power: %ld", capacity, energy, power);
|
||||
LOG_DBG("capacity: %ld, energy: %ld, power: %ld, charge=%ld, current=%ld, "
|
||||
"time-to-empty: %ld, time-to-full: %ld",
|
||||
capacity, energy, power, charge, current, time_to_empty, time_to_full);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
if (m->state != state) {
|
||||
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
|
||||
}
|
||||
m->state = state;
|
||||
m->capacity = capacity;
|
||||
m->energy = energy;
|
||||
m->power = power;
|
||||
m->charge = charge;
|
||||
if (current != -1) {
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
ema_linear(&m->ema_current, (struct current_state){current, current, t}, m->smoothing_scale);
|
||||
}
|
||||
m->time_to_empty = time_to_empty;
|
||||
m->time_to_full = time_to_full;
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -249,60 +482,106 @@ run(struct module *mod)
|
|||
const struct bar *bar = mod->bar;
|
||||
struct private *m = mod->private;
|
||||
|
||||
int base_dir_fd = initialize(m);
|
||||
if (base_dir_fd == -1)
|
||||
if (!initialize(m))
|
||||
return -1;
|
||||
|
||||
LOG_INFO("%s: %s %s (at %.1f%% of original capacity)",
|
||||
m->battery, m->manufacturer, m->model,
|
||||
100.0 * m->energy_full / m->energy_full_design);
|
||||
LOG_INFO("%s: %s %s (at %.1f%% of original capacity)", m->battery, m->manufacturer, m->model,
|
||||
(m->energy_full > 0 ? 100.0 * m->energy_full / m->energy_full_design
|
||||
: m->charge_full > 0 ? 100.0 * m->charge_full / m->charge_full_design
|
||||
: 0.0));
|
||||
|
||||
int ret = 1;
|
||||
int status_fd = openat(base_dir_fd, "status", O_RDONLY);
|
||||
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY);
|
||||
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY);
|
||||
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY);
|
||||
|
||||
struct udev *udev = udev_new();
|
||||
struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
|
||||
|
||||
if (status_fd == -1 || capacity_fd == -1 || energy_fd == -1 ||
|
||||
power_fd == -1 || udev == NULL || mon == NULL)
|
||||
{
|
||||
if (udev == NULL || mon == NULL)
|
||||
goto out;
|
||||
}
|
||||
|
||||
udev_monitor_filter_add_match_subsystem_devtype(mon, "power_supply", NULL);
|
||||
udev_monitor_enable_receiving(mon);
|
||||
|
||||
update_status(mod, capacity_fd, energy_fd, power_fd, status_fd);
|
||||
if (!update_status(mod))
|
||||
goto out;
|
||||
|
||||
bar->refresh(bar);
|
||||
|
||||
int timeout_left_ms = m->poll_interval;
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = udev_monitor_get_fd(mon), .events = POLLIN},
|
||||
};
|
||||
poll(fds, 2, m->poll_interval * 1000);
|
||||
|
||||
int timeout = m->poll_interval > 0 ? timeout_left_ms : -1;
|
||||
|
||||
struct timespec time_before_poll;
|
||||
if (clock_gettime(CLOCK_BOOTTIME, &time_before_poll) < 0) {
|
||||
LOG_ERRNO("failed to get current time");
|
||||
break;
|
||||
}
|
||||
|
||||
const int poll_ret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout);
|
||||
|
||||
if (poll_ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bool udev_for_us = false;
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
struct udev_device *dev = udev_monitor_receive_device(mon);
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
if (dev != NULL) {
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
udev_for_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
|
||||
|
||||
bool is_us = strcmp(sysname, m->battery) == 0;
|
||||
udev_device_unref(dev);
|
||||
if (!udev_for_us) {
|
||||
LOG_DBG("udev notification not for us (%s != %s)", m->battery,
|
||||
sysname != sysname ? sysname : "NULL");
|
||||
} else
|
||||
LOG_DBG("triggering update due to udev notification");
|
||||
|
||||
if (!is_us)
|
||||
continue;
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
}
|
||||
|
||||
update_status(mod, capacity_fd, energy_fd, power_fd, status_fd);
|
||||
bar->refresh(bar);
|
||||
if (udev_for_us || poll_ret == 0) {
|
||||
if (update_status(mod))
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
if (poll_ret == 0 || udev_for_us) {
|
||||
LOG_DBG("resetting timeout-left to %ldms", m->poll_interval);
|
||||
timeout_left_ms = m->poll_interval;
|
||||
} else {
|
||||
struct timespec time_after_poll;
|
||||
if (clock_gettime(CLOCK_BOOTTIME, &time_after_poll) < 0) {
|
||||
LOG_ERRNO("failed to get current time");
|
||||
break;
|
||||
}
|
||||
|
||||
struct timespec timeout_consumed;
|
||||
timespec_sub(&time_after_poll, &time_before_poll, &timeout_consumed);
|
||||
|
||||
const int timeout_consumed_ms = timeout_consumed.tv_sec * 1000 + timeout_consumed.tv_nsec / 1000000;
|
||||
|
||||
LOG_DBG("timeout-left before: %dms, consumed: %dms, updated: %dms", timeout_left_ms, timeout_consumed_ms,
|
||||
max(timeout_left_ms - timeout_consumed_ms, 0));
|
||||
|
||||
timeout_left_ms -= timeout_consumed_ms;
|
||||
if (timeout_left_ms < 0)
|
||||
timeout_left_ms = 0;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -310,34 +589,28 @@ out:
|
|||
udev_monitor_unref(mon);
|
||||
if (udev != NULL)
|
||||
udev_unref(udev);
|
||||
|
||||
if (power_fd != -1)
|
||||
close(power_fd);
|
||||
if (energy_fd != -1)
|
||||
close(energy_fd);
|
||||
if (capacity_fd != -1)
|
||||
close(capacity_fd);
|
||||
if (status_fd != -1)
|
||||
close(status_fd);
|
||||
|
||||
close(base_dir_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
battery_new(const char *battery, struct particle *label, int poll_interval_secs)
|
||||
battery_new(const char *battery, struct particle *label, long poll_interval_msecs, int battery_scale,
|
||||
long smoothing_secs)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->label = label;
|
||||
m->poll_interval = poll_interval_secs;
|
||||
m->poll_interval = poll_interval_msecs;
|
||||
m->battery_scale = battery_scale;
|
||||
m->smoothing_scale = smoothing_secs * one_sec_in_ns;
|
||||
m->battery = strdup(battery);
|
||||
m->state = STATE_DISCHARGING;
|
||||
m->state = STATE_UNKNOWN;
|
||||
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
@ -347,11 +620,29 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
const struct yml_node *name = yml_get_value(node, "name");
|
||||
const struct yml_node *poll_interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *battery_scale = yml_get_value(node, "battery-scale");
|
||||
const struct yml_node *smoothing_secs = yml_get_value(node, "smoothing-secs");
|
||||
|
||||
return battery_new(
|
||||
yml_value_as_string(name),
|
||||
conf_to_particle(c, inherited),
|
||||
poll_interval != NULL ? yml_value_as_int(poll_interval) : 60);
|
||||
return battery_new(yml_value_as_string(name), conf_to_particle(c, inherited),
|
||||
(poll_interval != NULL ? yml_value_as_int(poll_interval) : default_poll_interval),
|
||||
(battery_scale != NULL ? yml_value_as_int(battery_scale) : 1),
|
||||
(smoothing_secs != NULL ? yml_value_as_int(smoothing_secs) : 100));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
const long value = yml_value_as_int(node);
|
||||
|
||||
if (value != 0 && value < min_poll_interval) {
|
||||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -359,7 +650,9 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"name", true, &conf_verify_string},
|
||||
{"poll-interval", false, &conf_verify_int},
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
{"battery-scale", false, &conf_verify_unsigned},
|
||||
{"smoothing-secs", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
119
modules/clock.c
119
modules/clock.c
|
@ -1,19 +1,30 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define LOG_MODULE "clock"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
enum {
|
||||
UPDATE_GRANULARITY_SECONDS,
|
||||
UPDATE_GRANULARITY_MINUTES,
|
||||
} update_granularity;
|
||||
char *date_format;
|
||||
char *time_format;
|
||||
bool utc;
|
||||
};
|
||||
|
||||
static void
|
||||
|
@ -27,12 +38,18 @@ destroy(struct module *mod)
|
|||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "clock";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
time_t t = time(NULL);
|
||||
struct tm *tm = localtime(&t);
|
||||
struct tm *tm = m->utc ? gmtime(&t) : localtime(&t);
|
||||
|
||||
char date_str[1024];
|
||||
strftime(date_str, sizeof(date_str), m->date_format, tm);
|
||||
|
@ -41,8 +58,7 @@ content(struct module *mod)
|
|||
strftime(time_str, sizeof(time_str), m->time_format, tm);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){tag_new_string(mod, "time", time_str),
|
||||
tag_new_string(mod, "date", date_str)},
|
||||
.tags = (struct tag *[]){tag_new_string(mod, "time", time_str), tag_new_string(mod, "date", date_str)},
|
||||
.count = 2,
|
||||
};
|
||||
|
||||
|
@ -55,43 +71,103 @@ content(struct module *mod)
|
|||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
|
||||
while (true) {
|
||||
time_t now = time(NULL);
|
||||
time_t now_no_secs = now / 60 * 60;
|
||||
assert(now_no_secs % 60 == 0);
|
||||
int ret = 1;
|
||||
|
||||
time_t next_min = now_no_secs + 60;
|
||||
time_t timeout = next_min - now;
|
||||
assert(timeout >= 0 && timeout <= 60);
|
||||
while (true) {
|
||||
struct timespec _now;
|
||||
clock_gettime(CLOCK_REALTIME, &_now);
|
||||
|
||||
const struct timeval now = {
|
||||
.tv_sec = _now.tv_sec,
|
||||
.tv_usec = _now.tv_nsec / 1000,
|
||||
};
|
||||
|
||||
int timeout_ms = 1000;
|
||||
|
||||
switch (m->update_granularity) {
|
||||
case UPDATE_GRANULARITY_SECONDS: {
|
||||
const struct timeval next_second = {.tv_sec = now.tv_sec + 1, .tv_usec = 0};
|
||||
|
||||
struct timeval _timeout;
|
||||
timersub(&next_second, &now, &_timeout);
|
||||
|
||||
assert(_timeout.tv_sec == 0 || (_timeout.tv_sec == 1 && _timeout.tv_usec == 0));
|
||||
timeout_ms = _timeout.tv_usec / 1000;
|
||||
break;
|
||||
}
|
||||
|
||||
case UPDATE_GRANULARITY_MINUTES: {
|
||||
const struct timeval next_minute = {
|
||||
.tv_sec = now.tv_sec / 60 * 60 + 60,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
|
||||
struct timeval _timeout;
|
||||
timersub(&next_minute, &now, &_timeout);
|
||||
timeout_ms = _timeout.tv_sec * 1000 + _timeout.tv_usec / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add 1ms to account for rounding errors */
|
||||
timeout_ms++;
|
||||
|
||||
LOG_DBG("now: %lds %ldµs -> timeout: %dms", now.tv_sec, now.tv_usec, timeout_ms);
|
||||
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
poll(fds, 1, timeout * 1000);
|
||||
if (poll(fds, 1, timeout_ms) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
clock_new(struct particle *label, const char *date_format, const char *time_format)
|
||||
clock_new(struct particle *label, const char *date_format, const char *time_format, bool utc)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->label = label;
|
||||
m->date_format = strdup(date_format);
|
||||
m->time_format = strdup(time_format);
|
||||
m->utc = utc;
|
||||
|
||||
static const char *const seconds_formatters[] = {
|
||||
"%c", "%s", "%S", "%T", "%r", "%X",
|
||||
};
|
||||
|
||||
m->update_granularity = UPDATE_GRANULARITY_MINUTES;
|
||||
|
||||
for (size_t i = 0; i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]); i++) {
|
||||
if (strstr(time_format, seconds_formatters[i]) != NULL) {
|
||||
m->update_granularity = UPDATE_GRANULARITY_SECONDS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("using %s update granularity",
|
||||
(m->update_granularity == UPDATE_GRANULARITY_MINUTES ? "minutes" : "seconds"));
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
@ -101,11 +177,11 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
const struct yml_node *date_format = yml_get_value(node, "date-format");
|
||||
const struct yml_node *time_format = yml_get_value(node, "time-format");
|
||||
const struct yml_node *utc = yml_get_value(node, "utc");
|
||||
|
||||
return clock_new(
|
||||
conf_to_particle(c, inherited),
|
||||
date_format != NULL ? yml_value_as_string(date_format) : "%x",
|
||||
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M");
|
||||
return clock_new(conf_to_particle(c, inherited), date_format != NULL ? yml_value_as_string(date_format) : "%x",
|
||||
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M",
|
||||
utc != NULL ? yml_value_as_bool(utc) : false);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -114,6 +190,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
static const struct attr_info attrs[] = {
|
||||
{"date-format", false, &conf_verify_string},
|
||||
{"time-format", false, &conf_verify_string},
|
||||
{"utc", false, &conf_verify_bool},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
297
modules/cpu.c
Normal file
297
modules/cpu.c
Normal file
|
@ -0,0 +1,297 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOG_MODULE "cpu"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
static const long min_poll_interval = 250;
|
||||
|
||||
struct cpu_stats {
|
||||
uint32_t *prev_cores_idle;
|
||||
uint32_t *prev_cores_nidle;
|
||||
|
||||
uint32_t *cur_cores_idle;
|
||||
uint32_t *cur_cores_nidle;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *template;
|
||||
uint16_t interval;
|
||||
size_t core_count;
|
||||
struct cpu_stats cpu_stats;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
m->template->destroy(m->template);
|
||||
free(m->cpu_stats.prev_cores_idle);
|
||||
free(m->cpu_stats.prev_cores_nidle);
|
||||
free(m->cpu_stats.cur_cores_idle);
|
||||
free(m->cpu_stats.cur_cores_nidle);
|
||||
free(m);
|
||||
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "cpu";
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
get_cpu_nb_cores()
|
||||
{
|
||||
int nb_cores = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
LOG_DBG("CPU count: %d", nb_cores);
|
||||
|
||||
return nb_cores;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_proc_stat_line(const char *line, uint32_t *user, uint32_t *nice, uint32_t *system, uint32_t *idle,
|
||||
uint32_t *iowait, uint32_t *irq, uint32_t *softirq, uint32_t *steal, uint32_t *guest,
|
||||
uint32_t *guestnice)
|
||||
{
|
||||
int32_t core_id;
|
||||
if (line[sizeof("cpu") - 1] == ' ') {
|
||||
int read = sscanf(line,
|
||||
"cpu %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu32 " %" SCNu32 " %" SCNu32,
|
||||
user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice);
|
||||
return read == 10;
|
||||
} else {
|
||||
int read = sscanf(line,
|
||||
"cpu%" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32,
|
||||
&core_id, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice);
|
||||
return read == 11;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
get_cpu_usage_percent(const struct cpu_stats *cpu_stats, int8_t core_idx)
|
||||
{
|
||||
uint32_t prev_total = cpu_stats->prev_cores_idle[core_idx + 1] + cpu_stats->prev_cores_nidle[core_idx + 1];
|
||||
|
||||
uint32_t cur_total = cpu_stats->cur_cores_idle[core_idx + 1] + cpu_stats->cur_cores_nidle[core_idx + 1];
|
||||
|
||||
double totald = cur_total - prev_total;
|
||||
double nidled = cpu_stats->cur_cores_nidle[core_idx + 1] - cpu_stats->prev_cores_nidle[core_idx + 1];
|
||||
|
||||
double percent = (nidled * 100) / (totald + 1);
|
||||
return round(percent);
|
||||
}
|
||||
|
||||
static void
|
||||
refresh_cpu_stats(struct cpu_stats *cpu_stats, size_t core_count)
|
||||
{
|
||||
int32_t core = 0;
|
||||
uint32_t user = 0;
|
||||
uint32_t nice = 0;
|
||||
uint32_t system = 0;
|
||||
uint32_t idle = 0;
|
||||
uint32_t iowait = 0;
|
||||
uint32_t irq = 0;
|
||||
uint32_t softirq = 0;
|
||||
uint32_t steal = 0;
|
||||
uint32_t guest = 0;
|
||||
uint32_t guestnice = 0;
|
||||
|
||||
FILE *fp = NULL;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
|
||||
fp = fopen("/proc/stat", "re");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/stat");
|
||||
return;
|
||||
}
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1 && core <= core_count) {
|
||||
if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) {
|
||||
if (!parse_proc_stat_line(line, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest,
|
||||
&guestnice)) {
|
||||
LOG_ERR("unable to parse /proc/stat line");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
cpu_stats->prev_cores_idle[core] = cpu_stats->cur_cores_idle[core];
|
||||
cpu_stats->prev_cores_nidle[core] = cpu_stats->cur_cores_nidle[core];
|
||||
|
||||
cpu_stats->cur_cores_idle[core] = idle + iowait;
|
||||
cpu_stats->cur_cores_nidle[core] = user + nice + system + irq + softirq + steal;
|
||||
|
||||
core++;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
fclose(fp);
|
||||
free(line);
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const size_t list_count = m->core_count + 1;
|
||||
struct exposable *parts[list_count];
|
||||
|
||||
{
|
||||
uint8_t total_usage = get_cpu_usage_percent(&m->cpu_stats, -1);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_int(mod, "id", -1),
|
||||
tag_new_int_range(mod, "cpu", total_usage, 0, 100),
|
||||
},
|
||||
.count = 2,
|
||||
};
|
||||
|
||||
parts[0] = m->template->instantiate(m->template, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m->core_count; i++) {
|
||||
uint8_t core_usage = get_cpu_usage_percent(&m->cpu_stats, i);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_int(mod, "id", i),
|
||||
tag_new_int_range(mod, "cpu", core_usage, 0, 100),
|
||||
},
|
||||
.count = 2,
|
||||
};
|
||||
|
||||
parts[i + 1] = m->template->instantiate(m->template, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return dynlist_exposable_new(parts, list_count, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
|
||||
struct private *p = mod->private;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval);
|
||||
|
||||
if (res < 0) {
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
LOG_ERRNO("unable to poll abort fd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
refresh_cpu_stats(&p->cpu_stats, p->core_count);
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
cpu_new(uint16_t interval, struct particle *template)
|
||||
{
|
||||
uint32_t nb_cores = get_cpu_nb_cores();
|
||||
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->template = template;
|
||||
p->interval = interval;
|
||||
p->core_count = nb_cores;
|
||||
|
||||
p->cpu_stats.prev_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_nidle));
|
||||
p->cpu_stats.prev_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_idle));
|
||||
|
||||
p->cpu_stats.cur_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_nidle));
|
||||
p->cpu_stats.cur_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_idle));
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = p;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return cpu_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < min_poll_interval) {
|
||||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_cpu_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_cpu_iface")));
|
||||
#endif
|
13
modules/dbus.h
Normal file
13
modules/dbus.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
// This header provides an generic interface for different versions of
|
||||
// systemd-sdbus.
|
||||
|
||||
#if defined(HAVE_LIBSYSTEMD)
|
||||
#include <systemd/sd-bus.h>
|
||||
#elif defined(HAVE_LIBELOGIND)
|
||||
#include <elogind/sd-bus.h>
|
||||
#elif defined(HAVE_BASU)
|
||||
#include <basu/sd-bus.h>
|
||||
#endif
|
||||
|
350
modules/disk-io.c
Normal file
350
modules/disk-io.c
Normal file
|
@ -0,0 +1,350 @@
|
|||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <tllist.h>
|
||||
|
||||
#define LOG_MODULE "disk-io"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
static const long min_poll_interval = 250;
|
||||
|
||||
struct device_stats {
|
||||
char *name;
|
||||
bool is_disk;
|
||||
|
||||
uint64_t prev_sectors_read;
|
||||
uint64_t cur_sectors_read;
|
||||
|
||||
uint64_t prev_sectors_written;
|
||||
uint64_t cur_sectors_written;
|
||||
|
||||
uint32_t ios_in_progress;
|
||||
|
||||
bool exists;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
uint16_t interval;
|
||||
tll(struct device_stats *) devices;
|
||||
};
|
||||
|
||||
static bool
|
||||
is_disk(char const *name)
|
||||
{
|
||||
DIR *dir = opendir("/sys/block");
|
||||
if (dir == NULL) {
|
||||
LOG_ERRNO("failed to read /sys/block directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
|
||||
bool found = false;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(name, entry->d_name) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return found;
|
||||
}
|
||||
|
||||
static struct device_stats *
|
||||
new_device_stats(char const *name)
|
||||
{
|
||||
struct device_stats *dev = malloc(sizeof(*dev));
|
||||
dev->name = strdup(name);
|
||||
dev->is_disk = is_disk(name);
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void
|
||||
free_device_stats(struct device_stats *dev)
|
||||
{
|
||||
free(dev->name);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->label->destroy(m->label);
|
||||
tll_foreach(m->devices, it) { free_device_stats(it->item); }
|
||||
tll_free(m->devices);
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "disk-io";
|
||||
}
|
||||
|
||||
static void
|
||||
refresh_device_stats(struct private *m)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
|
||||
fp = fopen("/proc/diskstats", "re");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/diskstats");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Devices may be added or removed during the bar's lifetime, as external
|
||||
* block devices are connected or disconnected from the machine. /proc/diskstats
|
||||
* reports data only for the devices that are currently connected.
|
||||
*
|
||||
* This means that if we have a device that ISN'T in /proc/diskstats, it was
|
||||
* disconnected, and we need to remove it from the list.
|
||||
*
|
||||
* On the other hand, if a device IS in /proc/diskstats, but not in our list, we
|
||||
* must create a new device_stats struct and add it to the list.
|
||||
*
|
||||
* The 'exists' variable is what keep tracks of whether or not /proc/diskstats
|
||||
* is still reporting the device (i.e., it is still connected).
|
||||
*/
|
||||
tll_foreach(m->devices, it) { it->item->exists = false; }
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
/*
|
||||
* For an explanation of the fields below, see
|
||||
* https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
|
||||
*/
|
||||
uint8_t major_number = 0;
|
||||
uint8_t minor_number = 0;
|
||||
char *device_name = NULL;
|
||||
uint32_t completed_reads = 0;
|
||||
uint32_t merged_reads = 0;
|
||||
uint64_t sectors_read = 0;
|
||||
uint32_t reading_time = 0;
|
||||
uint32_t completed_writes = 0;
|
||||
uint32_t merged_writes = 0;
|
||||
uint64_t sectors_written = 0;
|
||||
uint32_t writting_time = 0;
|
||||
uint32_t ios_in_progress = 0;
|
||||
uint32_t io_time = 0;
|
||||
uint32_t io_weighted_time = 0;
|
||||
uint32_t completed_discards = 0;
|
||||
uint32_t merged_discards = 0;
|
||||
uint32_t sectors_discarded = 0;
|
||||
uint32_t discarding_time = 0;
|
||||
uint32_t completed_flushes = 0;
|
||||
uint32_t flushing_time = 0;
|
||||
if (!sscanf(line,
|
||||
" %" SCNu8 " %" SCNu8 " %ms %" SCNu32 " %" SCNu32 " %" SCNu64 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu64 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu32 " %" SCNu32 " %" SCNu32,
|
||||
&major_number, &minor_number, &device_name, &completed_reads, &merged_reads, §ors_read,
|
||||
&reading_time, &completed_writes, &merged_writes, §ors_written, &writting_time,
|
||||
&ios_in_progress, &io_time, &io_weighted_time, &completed_discards, &merged_discards,
|
||||
§ors_discarded, &discarding_time, &completed_flushes, &flushing_time)) {
|
||||
LOG_ERR("unable to parse /proc/diskstats line");
|
||||
free(device_name);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
tll_foreach(m->devices, it)
|
||||
{
|
||||
struct device_stats *dev = it->item;
|
||||
if (strcmp(dev->name, device_name) == 0) {
|
||||
dev->prev_sectors_read = dev->cur_sectors_read;
|
||||
dev->prev_sectors_written = dev->cur_sectors_written;
|
||||
dev->ios_in_progress = ios_in_progress;
|
||||
dev->cur_sectors_read = sectors_read;
|
||||
dev->cur_sectors_written = sectors_written;
|
||||
dev->exists = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
struct device_stats *new_dev = new_device_stats(device_name);
|
||||
new_dev->ios_in_progress = ios_in_progress;
|
||||
new_dev->prev_sectors_read = sectors_read;
|
||||
new_dev->cur_sectors_read = sectors_read;
|
||||
new_dev->prev_sectors_written = sectors_written;
|
||||
new_dev->cur_sectors_written = sectors_written;
|
||||
new_dev->exists = true;
|
||||
tll_push_back(m->devices, new_dev);
|
||||
}
|
||||
|
||||
free(device_name);
|
||||
}
|
||||
|
||||
tll_foreach(m->devices, it)
|
||||
{
|
||||
if (!it->item->exists) {
|
||||
free_device_stats(it->item);
|
||||
tll_remove(m->devices, it);
|
||||
}
|
||||
}
|
||||
exit:
|
||||
fclose(fp);
|
||||
free(line);
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *p = mod->private;
|
||||
uint64_t total_bytes_read = 0;
|
||||
uint64_t total_bytes_written = 0;
|
||||
uint32_t total_ios_in_progress = 0;
|
||||
mtx_lock(&mod->lock);
|
||||
struct exposable *tag_parts[p->devices.length + 1];
|
||||
int i = 0;
|
||||
tll_foreach(p->devices, it)
|
||||
{
|
||||
struct device_stats *dev = it->item;
|
||||
uint64_t bytes_read = (dev->cur_sectors_read - dev->prev_sectors_read) * 512;
|
||||
uint64_t bytes_written = (dev->cur_sectors_written - dev->prev_sectors_written) * 512;
|
||||
|
||||
if (dev->is_disk) {
|
||||
total_bytes_read += bytes_read;
|
||||
total_bytes_written += bytes_written;
|
||||
total_ios_in_progress += dev->ios_in_progress;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]) {
|
||||
tag_new_string(mod, "device", dev->name),
|
||||
tag_new_bool(mod, "is_disk", dev->is_disk),
|
||||
tag_new_int(mod, "read_speed", (bytes_read * 1000) / p->interval),
|
||||
tag_new_int(mod, "write_speed", (bytes_written * 1000) / p->interval),
|
||||
tag_new_int(mod, "ios_in_progress", dev->ios_in_progress),
|
||||
},
|
||||
.count = 5,
|
||||
};
|
||||
tag_parts[i++] = p->label->instantiate(p->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]) {
|
||||
tag_new_string(mod, "device", "Total"),
|
||||
tag_new_bool(mod, "is_disk", true),
|
||||
tag_new_int(mod, "read_speed", (total_bytes_read * 1000) / p->interval),
|
||||
tag_new_int(mod, "write_speed", (total_bytes_written * 1000) / p->interval),
|
||||
tag_new_int(mod, "ios_in_progress", total_ios_in_progress),
|
||||
},
|
||||
.count = 5,
|
||||
};
|
||||
tag_parts[i] = p->label->instantiate(p->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
return dynlist_exposable_new(tag_parts, p->devices.length + 1, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
struct private *p = mod->private;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval);
|
||||
|
||||
if (res < 0) {
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
LOG_ERRNO("unable to poll abort fd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
refresh_device_stats(p);
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
disk_io_new(uint16_t interval, struct particle *label)
|
||||
{
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->label = label;
|
||||
p->interval = interval;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = p;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return disk_io_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval),
|
||||
conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < min_poll_interval) {
|
||||
LOG_ERR("%s: poll-interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_disk_io_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_disk_io_iface")));
|
||||
#endif
|
550
modules/dwl.c
Normal file
550
modules/dwl.c
Normal file
|
@ -0,0 +1,550 @@
|
|||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ARR_LEN(x) (sizeof((x)) / sizeof((x)[0]))
|
||||
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../module.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#define LOG_MODULE "dwl"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
|
||||
struct dwl_tag {
|
||||
int id;
|
||||
char *name;
|
||||
bool selected;
|
||||
bool empty;
|
||||
bool urgent;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
|
||||
char const *monitor;
|
||||
|
||||
unsigned int number_of_tags;
|
||||
char *dwl_info_filename;
|
||||
|
||||
/* dwl data */
|
||||
char *title;
|
||||
char *appid;
|
||||
bool fullscreen;
|
||||
bool floating;
|
||||
bool selmon;
|
||||
tll(struct dwl_tag *) tags;
|
||||
char *layout;
|
||||
};
|
||||
|
||||
enum LINE_MODE {
|
||||
LINE_MODE_0,
|
||||
LINE_MODE_TITLE,
|
||||
LINE_MODE_APPID,
|
||||
LINE_MODE_FULLSCREEN,
|
||||
LINE_MODE_FLOATING,
|
||||
LINE_MODE_SELMON,
|
||||
LINE_MODE_TAGS,
|
||||
LINE_MODE_LAYOUT,
|
||||
};
|
||||
|
||||
static void
|
||||
free_dwl_tag(struct dwl_tag *tag)
|
||||
{
|
||||
free(tag->name);
|
||||
free(tag);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
private->label->destroy(private->label);
|
||||
|
||||
tll_free_and_free(private->tags, free_dwl_tag);
|
||||
free(private->dwl_info_filename);
|
||||
free(private->title);
|
||||
free(private->layout);
|
||||
free(private);
|
||||
|
||||
module_default_destroy(module);
|
||||
}
|
||||
|
||||
static char const *
|
||||
description(const struct module *module)
|
||||
{
|
||||
return "dwl";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *module)
|
||||
{
|
||||
struct private const *private = module->private;
|
||||
mtx_lock(&module->lock);
|
||||
|
||||
size_t i = 0;
|
||||
/* + 1 for `default` tag */
|
||||
struct exposable *exposable[tll_length(private->tags) + 1];
|
||||
tll_foreach(private->tags, it)
|
||||
{
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag*[]){
|
||||
tag_new_string(module, "title", private->title),
|
||||
tag_new_string(module, "appid", private->appid),
|
||||
tag_new_bool(module, "fullscreen", private->fullscreen),
|
||||
tag_new_bool(module, "floating", private->floating),
|
||||
tag_new_bool(module, "selmon", private->selmon),
|
||||
tag_new_string(module, "layout", private->layout),
|
||||
tag_new_int(module, "id", it->item->id),
|
||||
tag_new_string(module, "name", it->item->name),
|
||||
tag_new_bool(module, "selected", it->item->selected),
|
||||
tag_new_bool(module, "empty", it->item->empty),
|
||||
tag_new_bool(module, "urgent", it->item->urgent),
|
||||
},
|
||||
.count = 11,
|
||||
};
|
||||
exposable[i++] = private->label->instantiate(private->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
/* default tag (used for title, layout, etc) */
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag*[]){
|
||||
tag_new_string(module, "title", private->title),
|
||||
tag_new_string(module, "appid", private->appid),
|
||||
tag_new_bool(module, "fullscreen", private->fullscreen),
|
||||
tag_new_bool(module, "floating", private->floating),
|
||||
tag_new_bool(module, "selmon", private->selmon),
|
||||
tag_new_string(module, "layout", private->layout),
|
||||
tag_new_int(module, "id", 0),
|
||||
tag_new_string(module, "name", "0"),
|
||||
tag_new_bool(module, "selected", false),
|
||||
tag_new_bool(module, "empty", true),
|
||||
tag_new_bool(module, "urgent", false),
|
||||
},
|
||||
.count = 11,
|
||||
};
|
||||
exposable[i++] = private->label->instantiate(private->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
|
||||
mtx_unlock(&module->lock);
|
||||
return dynlist_exposable_new(exposable, i, 0, 0);
|
||||
}
|
||||
|
||||
static struct dwl_tag *
|
||||
dwl_tag_from_id(struct private *private, uint32_t id)
|
||||
{
|
||||
tll_foreach(private->tags, it)
|
||||
{
|
||||
if (it->item->id == id)
|
||||
return it->item;
|
||||
}
|
||||
|
||||
assert(false); /* unreachable */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
process_line(char *line, struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
enum LINE_MODE line_mode = LINE_MODE_0;
|
||||
|
||||
/* Remove \n */
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
|
||||
/* Split line by space */
|
||||
size_t index = 1;
|
||||
char *save_pointer = NULL;
|
||||
char *string = strtok_r(line, " ", &save_pointer);
|
||||
while (string != NULL) {
|
||||
/* dwl logs are formatted like this
|
||||
* $1 -> monitor
|
||||
* $2 -> action
|
||||
* $3 -> arg1
|
||||
* $4 -> arg2
|
||||
* ... */
|
||||
|
||||
/* monitor */
|
||||
if (index == 1) {
|
||||
/* Not our monitor */
|
||||
if (strcmp(string, private->monitor) != 0)
|
||||
break;
|
||||
}
|
||||
/* action */
|
||||
else if (index == 2) {
|
||||
if (strcmp(string, "title") == 0) {
|
||||
line_mode = LINE_MODE_TITLE;
|
||||
/* Update the title here, to avoid allocate and free memory on
|
||||
* every iteration (the line is separated by spaces, then we
|
||||
* join it again) a bit suboptimal, isn't it?) */
|
||||
free(private->title);
|
||||
private->title = strdup(save_pointer);
|
||||
break;
|
||||
} else if (strcmp(string, "appid") == 0) {
|
||||
line_mode = LINE_MODE_APPID;
|
||||
/* Update the appid here, same as the title. */
|
||||
free(private->appid);
|
||||
private->appid = strdup(save_pointer);
|
||||
break;
|
||||
} else if (strcmp(string, "fullscreen") == 0)
|
||||
line_mode = LINE_MODE_FULLSCREEN;
|
||||
else if (strcmp(string, "floating") == 0)
|
||||
line_mode = LINE_MODE_FLOATING;
|
||||
else if (strcmp(string, "selmon") == 0)
|
||||
line_mode = LINE_MODE_SELMON;
|
||||
else if (strcmp(string, "tags") == 0)
|
||||
line_mode = LINE_MODE_TAGS;
|
||||
else if (strcmp(string, "layout") == 0)
|
||||
line_mode = LINE_MODE_LAYOUT;
|
||||
else {
|
||||
LOG_WARN("UNKNOWN action, please open an issue on https://codeberg.org/dnkl/yambar");
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* args */
|
||||
else {
|
||||
if (line_mode == LINE_MODE_TAGS) {
|
||||
static uint32_t occupied, selected, client_tags, urgent;
|
||||
static uint32_t *target = NULL;
|
||||
|
||||
/* dwl tags action log are formatted like this
|
||||
* $3 -> occupied
|
||||
* $4 -> tags
|
||||
* $5 -> clientTags (not needed)
|
||||
* $6 -> urgent */
|
||||
if (index == 3)
|
||||
target = &occupied;
|
||||
else if (index == 4)
|
||||
target = &selected;
|
||||
else if (index == 5)
|
||||
target = &client_tags;
|
||||
else if (index == 6)
|
||||
target = &urgent;
|
||||
|
||||
/* No need to check error IMHO */
|
||||
*target = strtoul(string, NULL, 10);
|
||||
|
||||
/* Populate information */
|
||||
if (index == 6) {
|
||||
for (size_t id = 1; id <= private->number_of_tags; ++id) {
|
||||
uint32_t mask = 1 << (id - 1);
|
||||
|
||||
struct dwl_tag *dwl_tag = dwl_tag_from_id(private, id);
|
||||
dwl_tag->selected = mask & selected;
|
||||
dwl_tag->empty = !(mask & occupied);
|
||||
dwl_tag->urgent = mask & urgent;
|
||||
}
|
||||
}
|
||||
} else
|
||||
switch (line_mode) {
|
||||
case LINE_MODE_TITLE:
|
||||
case LINE_MODE_APPID:
|
||||
assert(false); /* unreachable */
|
||||
break;
|
||||
case LINE_MODE_FULLSCREEN:
|
||||
private
|
||||
->fullscreen = (strcmp(string, "0") != 0);
|
||||
break;
|
||||
case LINE_MODE_FLOATING:
|
||||
private
|
||||
->floating = (strcmp(string, "0") != 0);
|
||||
break;
|
||||
case LINE_MODE_SELMON:
|
||||
private
|
||||
->selmon = (strcmp(string, "0") != 0);
|
||||
break;
|
||||
case LINE_MODE_LAYOUT:
|
||||
free(private->layout);
|
||||
private->layout = strdup(string);
|
||||
break;
|
||||
default:;
|
||||
assert(false); /* unreachable */
|
||||
}
|
||||
}
|
||||
|
||||
string = strtok_r(NULL, " ", &save_pointer);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
file_read_content(FILE *file, struct module *module)
|
||||
{
|
||||
static char buffer[1024];
|
||||
|
||||
errno = 0;
|
||||
while (fgets(buffer, ARR_LEN(buffer), file) != NULL)
|
||||
process_line(buffer, module);
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
|
||||
/* Check whether error has been */
|
||||
if (ferror(file) != 0) {
|
||||
LOG_ERRNO("unable to read file's content.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
file_seek_to_last_n_lines(FILE *file, int number_of_lines)
|
||||
{
|
||||
if (number_of_lines == 0 || file == NULL)
|
||||
return;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
|
||||
long position = ftell(file);
|
||||
while (position > 0) {
|
||||
/* Cannot go less than position 0 */
|
||||
if (fseek(file, --position, SEEK_SET) == EINVAL)
|
||||
break;
|
||||
|
||||
if (fgetc(file) == '\n')
|
||||
if (number_of_lines-- == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
run_init(int *inotify_fd, int *inotify_wd, FILE **file, char *dwl_info_filename)
|
||||
{
|
||||
*inotify_fd = inotify_init();
|
||||
if (*inotify_fd == -1) {
|
||||
LOG_ERRNO("unable to create inotify fd.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*inotify_wd = inotify_add_watch(*inotify_fd, dwl_info_filename, IN_MODIFY);
|
||||
if (*inotify_wd == -1) {
|
||||
close(*inotify_fd);
|
||||
LOG_ERRNO("unable to add watch to inotify fd.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
*file = fopen(dwl_info_filename, "re");
|
||||
if (*file == NULL) {
|
||||
inotify_rm_watch(*inotify_fd, *inotify_wd);
|
||||
close(*inotify_fd);
|
||||
LOG_ERRNO("unable to open file.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_clean(int inotify_fd, int inotify_wd, FILE *file)
|
||||
{
|
||||
if (inotify_fd != -1) {
|
||||
if (inotify_wd != -1)
|
||||
inotify_rm_watch(inotify_fd, inotify_wd);
|
||||
close(inotify_fd);
|
||||
}
|
||||
|
||||
if (file != NULL) {
|
||||
if (fclose(file) == EOF) {
|
||||
LOG_ERRNO("unable to close file.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int
|
||||
run(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
|
||||
/* Ugly, but I didn't find better way for waiting
|
||||
* the monitor's name to be set */
|
||||
do {
|
||||
private->monitor = module->bar->output_name(module->bar);
|
||||
usleep(50);
|
||||
} while (private->monitor == NULL);
|
||||
|
||||
int inotify_fd = -1, inotify_wd = -1;
|
||||
FILE *file = NULL;
|
||||
if (run_init(&inotify_fd, &inotify_wd, &file, private->dwl_info_filename) != 0)
|
||||
return 1;
|
||||
|
||||
/* Dwl output is 6 lines per monitor, so let's assume that nobody has
|
||||
* more than 5 monitors (6 * 5 = 30) */
|
||||
mtx_lock(&module->lock);
|
||||
file_seek_to_last_n_lines(file, 30);
|
||||
if (file_read_content(file, module) != 0) {
|
||||
mtx_unlock(&module->lock);
|
||||
return run_clean(inotify_fd, inotify_wd, file);
|
||||
}
|
||||
mtx_unlock(&module->lock);
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
(struct pollfd){.fd = module->abort_fd, .events = POLLIN},
|
||||
(struct pollfd){.fd = inotify_fd, .events = POLLIN},
|
||||
};
|
||||
|
||||
if (poll(fds, ARR_LEN(fds), -1) == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("unable to poll.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
/* fds[1] (inotify_fd) must be POLLIN otherwise issue happen'd */
|
||||
if (!(fds[1].revents & POLLIN)) {
|
||||
LOG_ERR("expected POLLIN revent");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Block until event */
|
||||
static char buffer[1024];
|
||||
ssize_t length = read(inotify_fd, buffer, ARR_LEN(buffer));
|
||||
|
||||
if (length == 0)
|
||||
break;
|
||||
|
||||
if (length == -1) {
|
||||
if (errno == EAGAIN)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("unable to read %s", private->dwl_info_filename);
|
||||
break;
|
||||
}
|
||||
|
||||
mtx_lock(&module->lock);
|
||||
if (file_read_content(file, module) != 0) {
|
||||
mtx_unlock(&module->lock);
|
||||
break;
|
||||
}
|
||||
mtx_unlock(&module->lock);
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
}
|
||||
|
||||
return run_clean(inotify_fd, inotify_wd, file);
|
||||
}
|
||||
|
||||
static struct module *
|
||||
dwl_new(struct particle *label, int number_of_tags, struct yml_node const *name_of_tags, char const *dwl_info_filename)
|
||||
{
|
||||
struct private *private = calloc(1, sizeof(struct private));
|
||||
private->label = label;
|
||||
private->number_of_tags = number_of_tags;
|
||||
private->dwl_info_filename = strdup(dwl_info_filename);
|
||||
|
||||
struct yml_list_iter list = {0};
|
||||
if (name_of_tags)
|
||||
list = yml_list_iter(name_of_tags);
|
||||
|
||||
for (int i = 1; i <= number_of_tags; i++) {
|
||||
struct dwl_tag *dwl_tag = calloc(1, sizeof(struct dwl_tag));
|
||||
dwl_tag->id = i;
|
||||
if (list.node) {
|
||||
dwl_tag->name = strdup(yml_value_as_string(list.node));
|
||||
yml_list_next(&list);
|
||||
} else if (asprintf(&dwl_tag->name, "%d", i) < 0) {
|
||||
LOG_ERRNO("asprintf");
|
||||
}
|
||||
tll_push_back(private->tags, dwl_tag);
|
||||
}
|
||||
|
||||
struct module *module = module_common_new();
|
||||
module->private = private;
|
||||
module->run = &run;
|
||||
module->destroy = &destroy;
|
||||
module->content = &content;
|
||||
module->description = &description;
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(struct yml_node const *node, struct conf_inherit inherited)
|
||||
{
|
||||
struct yml_node const *content = yml_get_value(node, "content");
|
||||
struct yml_node const *number_of_tags = yml_get_value(node, "number-of-tags");
|
||||
struct yml_node const *name_of_tags = yml_get_value(node, "name-of-tags");
|
||||
struct yml_node const *dwl_info_filename = yml_get_value(node, "dwl-info-filename");
|
||||
|
||||
return dwl_new(conf_to_particle(content, inherited), yml_value_as_int(number_of_tags), name_of_tags,
|
||||
yml_value_as_string(dwl_info_filename));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_names(keychain_t *keychain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_list(node)) {
|
||||
LOG_ERR("%s: %s is not a list", conf_err_prefix(keychain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
return conf_verify_list(keychain, node, &conf_verify_string);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *keychain, struct yml_node const *node)
|
||||
{
|
||||
|
||||
static struct attr_info const attrs[] = {
|
||||
{"number-of-tags", true, &conf_verify_unsigned},
|
||||
{"name-of-tags", false, &verify_names},
|
||||
{"dwl-info-filename", true, &conf_verify_string},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
if (!conf_verify_dict(keychain, node, attrs))
|
||||
return false;
|
||||
|
||||
/* No need to check whether is `number_of_tags` is a int
|
||||
* because `conf_verify_unsigned` already did it */
|
||||
struct yml_node const *ntags_key = yml_get_key(node, "number-of-tags");
|
||||
struct yml_node const *value = yml_get_value(node, "number-of-tags");
|
||||
int number_of_tags = yml_value_as_int(value);
|
||||
if (number_of_tags == 0) {
|
||||
LOG_ERR("%s: %s must not be 0", conf_err_prefix(keychain, ntags_key), yml_value_as_string(ntags_key));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct yml_node const *key = yml_get_key(node, "name-of-tags");
|
||||
value = yml_get_value(node, "name-of-tags");
|
||||
if (value && yml_list_length(value) != number_of_tags) {
|
||||
LOG_ERR("%s: %s must have the same number of elements that %s", conf_err_prefix(keychain, key),
|
||||
yml_value_as_string(key), yml_value_as_string(ntags_key));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No need to check whether is `dwl_info_filename` is a string
|
||||
* because `conf_verify_string` already did it */
|
||||
key = yml_get_key(node, "dwl-info-filename");
|
||||
value = yml_get_value(node, "dwl-info-filename");
|
||||
if (strlen(yml_value_as_string(value)) == 0) {
|
||||
LOG_ERR("%s: %s must not be empty", conf_err_prefix(keychain, key), yml_value_as_string(key));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct module_iface const module_dwl_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern struct module_iface const iface __attribute__((weak, alias("module_dwl_iface")));
|
||||
#endif
|
666
modules/foreign-toplevel.c
Normal file
666
modules/foreign-toplevel.c
Normal file
|
@ -0,0 +1,666 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include <tllist.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#define LOG_MODULE "foreign-toplevel"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#include "wlr-foreign-toplevel-management-unstable-v1.h"
|
||||
#include "xdg-output-unstable-v1.h"
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
static const int required_manager_interface_version = 2;
|
||||
|
||||
struct output {
|
||||
struct module *mod;
|
||||
|
||||
uint32_t wl_name;
|
||||
struct wl_output *wl_output;
|
||||
struct zxdg_output_v1 *xdg_output;
|
||||
|
||||
char *name;
|
||||
};
|
||||
|
||||
struct toplevel {
|
||||
struct module *mod;
|
||||
struct zwlr_foreign_toplevel_handle_v1 *handle;
|
||||
|
||||
char *app_id;
|
||||
char *title;
|
||||
|
||||
bool maximized;
|
||||
bool minimized;
|
||||
bool activated;
|
||||
bool fullscreen;
|
||||
|
||||
tll(const struct output *) outputs;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *template;
|
||||
uint32_t manager_wl_name;
|
||||
struct zwlr_foreign_toplevel_manager_v1 *manager;
|
||||
struct zxdg_output_manager_v1 *xdg_output_manager;
|
||||
|
||||
bool all_monitors;
|
||||
tll(struct toplevel) toplevels;
|
||||
tll(struct output) outputs;
|
||||
};
|
||||
|
||||
static void
|
||||
output_free(struct output *output)
|
||||
{
|
||||
free(output->name);
|
||||
if (output->xdg_output != NULL)
|
||||
zxdg_output_v1_destroy(output->xdg_output);
|
||||
if (output->wl_output != NULL)
|
||||
wl_output_release(output->wl_output);
|
||||
}
|
||||
|
||||
static void
|
||||
toplevel_free(struct toplevel *top)
|
||||
{
|
||||
if (top->handle != NULL)
|
||||
zwlr_foreign_toplevel_handle_v1_destroy(top->handle);
|
||||
|
||||
free(top->app_id);
|
||||
free(top->title);
|
||||
tll_free(top->outputs);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->template->destroy(m->template);
|
||||
|
||||
assert(tll_length(m->toplevels) == 0);
|
||||
assert(tll_length(m->outputs) == 0);
|
||||
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "toplevel";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const size_t toplevel_count = tll_length(m->toplevels);
|
||||
size_t show_count = 0;
|
||||
struct exposable *toplevels[toplevel_count];
|
||||
|
||||
const char *current_output = mod->bar->output_name(mod->bar);
|
||||
|
||||
tll_foreach(m->toplevels, it)
|
||||
{
|
||||
const struct toplevel *top = &it->item;
|
||||
|
||||
bool show = false;
|
||||
|
||||
if (m->all_monitors)
|
||||
show = true;
|
||||
else if (current_output != NULL) {
|
||||
tll_foreach(top->outputs, it2)
|
||||
{
|
||||
const struct output *output = it2->item;
|
||||
if (output->name != NULL && strcmp(output->name, current_output) == 0) {
|
||||
show = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!show)
|
||||
continue;
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_string(mod, "app-id", it->item.app_id),
|
||||
tag_new_string(mod, "title", it->item.title),
|
||||
tag_new_bool(mod, "maximized", it->item.maximized),
|
||||
tag_new_bool(mod, "minimized", it->item.minimized),
|
||||
tag_new_bool(mod, "activated", it->item.activated),
|
||||
tag_new_bool(mod, "fullscreen", it->item.fullscreen),
|
||||
},
|
||||
.count = 6,
|
||||
};
|
||||
|
||||
toplevels[show_count++] = m->template->instantiate(m->template, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return dynlist_exposable_new(toplevels, show_count, 0, 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
|
||||
{
|
||||
if (version >= wanted)
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name)
|
||||
{
|
||||
struct output *output = data;
|
||||
struct module *mod = output->mod;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
{
|
||||
free(output->name);
|
||||
output->name = name != NULL ? strdup(name) : NULL;
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description)
|
||||
{
|
||||
}
|
||||
|
||||
static struct zxdg_output_v1_listener xdg_output_listener = {
|
||||
.logical_position = xdg_output_handle_logical_position,
|
||||
.logical_size = xdg_output_handle_logical_size,
|
||||
.done = xdg_output_handle_done,
|
||||
.name = xdg_output_handle_name,
|
||||
.description = xdg_output_handle_description,
|
||||
};
|
||||
|
||||
static void
|
||||
title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
|
||||
mtx_lock(&top->mod->lock);
|
||||
{
|
||||
free(top->title);
|
||||
top->title = title != NULL ? strdup(title) : NULL;
|
||||
}
|
||||
mtx_unlock(&top->mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
|
||||
mtx_lock(&top->mod->lock);
|
||||
{
|
||||
free(top->app_id);
|
||||
top->app_id = app_id != NULL ? strdup(app_id) : NULL;
|
||||
}
|
||||
mtx_unlock(&top->mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
struct module *mod = top->mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const struct output *output = NULL;
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
if (it->item.wl_output == wl_output) {
|
||||
output = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (output == NULL) {
|
||||
LOG_ERR("output-enter event on untracked output");
|
||||
goto out;
|
||||
}
|
||||
|
||||
tll_foreach(top->outputs, it)
|
||||
{
|
||||
if (it->item == output) {
|
||||
LOG_ERR("output-enter event on output we're already on");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("mapped: %s:%s on %s", top->app_id, top->title, output->name);
|
||||
tll_push_back(top->outputs, output);
|
||||
|
||||
out:
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
struct module *mod = top->mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const struct output *output = NULL;
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
if (it->item.wl_output == wl_output) {
|
||||
output = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (output == NULL) {
|
||||
LOG_ERR("output-leave event on untracked output");
|
||||
goto out;
|
||||
}
|
||||
|
||||
bool output_removed = false;
|
||||
tll_foreach(top->outputs, it)
|
||||
{
|
||||
if (it->item == output) {
|
||||
LOG_DBG("unmapped: %s:%s from %s", top->app_id, top->title, output->name);
|
||||
tll_remove(top->outputs, it);
|
||||
output_removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!output_removed) {
|
||||
LOG_ERR("output-leave event on an output we're not on");
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *states)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
|
||||
bool maximized = false;
|
||||
bool minimized = false;
|
||||
bool activated = false;
|
||||
bool fullscreen = false;
|
||||
|
||||
enum zwlr_foreign_toplevel_handle_v1_state *state;
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
switch (*state) {
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED:
|
||||
maximized = true;
|
||||
break;
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED:
|
||||
minimized = true;
|
||||
break;
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED:
|
||||
activated = true;
|
||||
break;
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN:
|
||||
fullscreen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mtx_lock(&top->mod->lock);
|
||||
{
|
||||
top->maximized = maximized;
|
||||
top->minimized = minimized;
|
||||
top->activated = activated;
|
||||
top->fullscreen = fullscreen;
|
||||
}
|
||||
mtx_unlock(&top->mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
const struct bar *bar = top->mod->bar;
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
static void
|
||||
closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
struct module *mod = top->mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
tll_foreach(m->toplevels, it)
|
||||
{
|
||||
if (it->item.handle == handle) {
|
||||
toplevel_free(top);
|
||||
tll_remove(m->toplevels, it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
static void
|
||||
parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_listener = {
|
||||
.title = &title,
|
||||
.app_id = &app_id,
|
||||
.output_enter = &output_enter,
|
||||
.output_leave = &output_leave,
|
||||
.state = &state,
|
||||
.done = &done,
|
||||
.closed = &closed,
|
||||
.parent = &parent,
|
||||
};
|
||||
|
||||
static void
|
||||
toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
struct toplevel toplevel = {
|
||||
.mod = mod,
|
||||
.handle = handle,
|
||||
};
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
{
|
||||
tll_push_back(m->toplevels, toplevel);
|
||||
|
||||
zwlr_foreign_toplevel_handle_v1_add_listener(handle, &toplevel_listener, &tll_back(m->toplevels));
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
assert(m->manager == manager);
|
||||
zwlr_foreign_toplevel_manager_v1_destroy(m->manager);
|
||||
m->manager = NULL;
|
||||
}
|
||||
|
||||
static const struct zwlr_foreign_toplevel_manager_v1_listener manager_listener = {
|
||||
.toplevel = &toplevel,
|
||||
.finished = &finished,
|
||||
};
|
||||
|
||||
static void
|
||||
output_xdg_output(struct output *output)
|
||||
{
|
||||
struct private *m = output->mod->private;
|
||||
|
||||
if (m->xdg_output_manager == NULL)
|
||||
return;
|
||||
if (output->xdg_output != NULL)
|
||||
return;
|
||||
|
||||
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(m->xdg_output_manager, output->wl_output);
|
||||
zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
|
||||
if (!verify_iface_version(interface, version, required_manager_interface_version))
|
||||
return;
|
||||
|
||||
m->manager_wl_name = name;
|
||||
}
|
||||
|
||||
else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
const uint32_t required = 3;
|
||||
if (!verify_iface_version(interface, version, required))
|
||||
return;
|
||||
|
||||
struct output output = {
|
||||
.mod = mod,
|
||||
.wl_name = name,
|
||||
.wl_output = wl_registry_bind(registry, name, &wl_output_interface, required),
|
||||
};
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
tll_push_back(m->outputs, output);
|
||||
output_xdg_output(&tll_back(m->outputs));
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
||||
const uint32_t required = 2;
|
||||
if (!verify_iface_version(interface, version, required))
|
||||
return;
|
||||
|
||||
m->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, required);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
tll_foreach(m->outputs, it) output_xdg_output(&it->item);
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
const struct output *output = &it->item;
|
||||
if (output->wl_name == name) {
|
||||
|
||||
/* Loop all toplevels */
|
||||
tll_foreach(m->toplevels, it2)
|
||||
{
|
||||
|
||||
/* And remove this output from their list of tracked
|
||||
* outputs */
|
||||
tll_foreach(it2->item.outputs, it3)
|
||||
{
|
||||
if (it3->item == output) {
|
||||
tll_remove(it2->item.outputs, it3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tll_remove(m->outputs, it);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = &handle_global,
|
||||
.global_remove = &handle_global_remove,
|
||||
};
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
int ret = -1;
|
||||
|
||||
struct wl_display *display = NULL;
|
||||
struct wl_registry *registry = NULL;
|
||||
|
||||
if ((display = wl_display_connect(NULL)) == NULL) {
|
||||
LOG_ERR("no Wayland compositor running");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((registry = wl_display_get_registry(display)) == NULL
|
||||
|| wl_registry_add_listener(registry, ®istry_listener, mod) != 0) {
|
||||
LOG_ERR("failed to get Wayland registry");
|
||||
goto out;
|
||||
}
|
||||
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
if (m->manager_wl_name == 0) {
|
||||
LOG_ERR("compositor does not implement the foreign-toplevel-manager interface");
|
||||
goto out;
|
||||
}
|
||||
|
||||
m->manager = wl_registry_bind(registry, m->manager_wl_name, &zwlr_foreign_toplevel_manager_v1_interface,
|
||||
required_manager_interface_version);
|
||||
|
||||
zwlr_foreign_toplevel_manager_v1_add_listener(m->manager, &manager_listener, mod);
|
||||
|
||||
while (true) {
|
||||
wl_display_flush(display);
|
||||
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = wl_display_get_fd(display), .events = POLLIN},
|
||||
};
|
||||
|
||||
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & (POLLIN | POLLHUP)) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_ERR("disconnected from the Wayland compositor");
|
||||
break;
|
||||
}
|
||||
|
||||
assert(fds[1].revents & POLLIN);
|
||||
wl_display_dispatch(display);
|
||||
}
|
||||
|
||||
out:
|
||||
tll_foreach(m->toplevels, it)
|
||||
{
|
||||
toplevel_free(&it->item);
|
||||
tll_remove(m->toplevels, it);
|
||||
}
|
||||
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
output_free(&it->item);
|
||||
tll_remove(m->outputs, it);
|
||||
}
|
||||
|
||||
if (m->xdg_output_manager != NULL)
|
||||
zxdg_output_manager_v1_destroy(m->xdg_output_manager);
|
||||
if (m->manager != NULL)
|
||||
zwlr_foreign_toplevel_manager_v1_destroy(m->manager);
|
||||
if (registry != NULL)
|
||||
wl_registry_destroy(registry);
|
||||
if (display != NULL)
|
||||
wl_display_disconnect(display);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
ftop_new(struct particle *label, bool all_monitors)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->template = label;
|
||||
m->all_monitors = all_monitors;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
const struct yml_node *all_monitors = yml_get_value(node, "all-monitors");
|
||||
|
||||
return ftop_new(conf_to_particle(c, inherited), all_monitors != NULL ? yml_value_as_bool(all_monitors) : false);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"all-monitors", false, &conf_verify_bool},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_foreign_toplevel_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_foreign_toplevel_iface")));
|
||||
#endif
|
|
@ -1,15 +1,15 @@
|
|||
#include "i3-common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#endif
|
||||
|
||||
#include <json-c/json_tokener.h>
|
||||
|
@ -19,7 +19,7 @@
|
|||
#include "../log.h"
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include "../xcb.h"
|
||||
#include "../xcb.h"
|
||||
#endif
|
||||
|
||||
#include "i3-ipc.h"
|
||||
|
@ -41,14 +41,11 @@ get_socket_address_x11(struct sockaddr_un *addr)
|
|||
xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH");
|
||||
assert(atom != XCB_ATOM_NONE);
|
||||
|
||||
xcb_get_property_cookie_t cookie
|
||||
= xcb_get_property_unchecked(
|
||||
conn, false, screen->root, atom,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path));
|
||||
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(conn, false, screen->root, atom,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path));
|
||||
|
||||
xcb_generic_error_t *err = NULL;
|
||||
xcb_get_property_reply_t *reply =
|
||||
xcb_get_property_reply(conn, cookie, &err);
|
||||
xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, cookie, &err);
|
||||
bool ret = false;
|
||||
|
||||
if (err != NULL) {
|
||||
|
@ -102,11 +99,7 @@ bool
|
|||
i3_send_pkg(int sock, int cmd, char *data)
|
||||
{
|
||||
const size_t size = data != NULL ? strlen(data) : 0;
|
||||
const i3_ipc_header_t hdr = {
|
||||
.magic = I3_IPC_MAGIC,
|
||||
.size = size,
|
||||
.type = cmd
|
||||
};
|
||||
const i3_ipc_header_t hdr = {.magic = I3_IPC_MAGIC, .size = size, .type = cmd};
|
||||
|
||||
if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr))
|
||||
return false;
|
||||
|
@ -120,8 +113,7 @@ i3_send_pkg(int sock, int cmd, char *data)
|
|||
}
|
||||
|
||||
bool
|
||||
i3_receive_loop(int abort_fd, int sock,
|
||||
const struct i3_ipc_callbacks *cbs, void *data)
|
||||
i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void *data)
|
||||
{
|
||||
/* Initial reply typically requires a couple of KB. But we often
|
||||
* need more later. For example, switching workspaces can result
|
||||
|
@ -133,10 +125,7 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
bool err = false;
|
||||
|
||||
while (!err) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = abort_fd, .events = POLLIN},
|
||||
{.fd = sock, .events = POLLIN}
|
||||
};
|
||||
struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}, {.fd = sock, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, 2, -1);
|
||||
if (res <= 0) {
|
||||
|
@ -159,13 +148,11 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
|
||||
/* Grow receive buffer, if necessary */
|
||||
if (buf_idx == reply_buf_size) {
|
||||
LOG_DBG("growing reply buffer: %zu -> %zu",
|
||||
reply_buf_size, reply_buf_size * 2);
|
||||
LOG_DBG("growing reply buffer: %zu -> %zu", reply_buf_size, reply_buf_size * 2);
|
||||
|
||||
char *new_buf = realloc(buf, reply_buf_size * 2);
|
||||
if (new_buf == NULL) {
|
||||
LOG_ERR("failed to grow reply buffer from %zu to %zu bytes",
|
||||
reply_buf_size, reply_buf_size * 2);
|
||||
LOG_ERR("failed to grow reply buffer from %zu to %zu bytes", reply_buf_size, reply_buf_size * 2);
|
||||
err = true;
|
||||
break;
|
||||
}
|
||||
|
@ -188,10 +175,8 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
while (!err && buf_idx >= sizeof(i3_ipc_header_t)) {
|
||||
const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf;
|
||||
if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) {
|
||||
LOG_ERR(
|
||||
"i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"",
|
||||
(int)sizeof(hdr->magic), I3_IPC_MAGIC,
|
||||
(int)sizeof(hdr->magic), hdr->magic);
|
||||
LOG_ERR("i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"", (int)sizeof(hdr->magic),
|
||||
I3_IPC_MAGIC, (int)sizeof(hdr->magic), hdr->magic);
|
||||
|
||||
err = true;
|
||||
break;
|
||||
|
@ -210,10 +195,10 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
char json_str[hdr->size + 1];
|
||||
memcpy(json_str, &buf[sizeof(*hdr)], hdr->size);
|
||||
json_str[hdr->size] = '\0';
|
||||
//printf("raw: %s\n", json_str);
|
||||
// printf("raw: %s\n", json_str);
|
||||
LOG_DBG("raw: %s\n", json_str);
|
||||
|
||||
//json_tokener *tokener = json_tokener_new();
|
||||
// json_tokener *tokener = json_tokener_new();
|
||||
struct json_object *json = json_tokener_parse(json_str);
|
||||
if (json == NULL) {
|
||||
LOG_ERR("failed to parse json");
|
||||
|
@ -262,13 +247,13 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
break;
|
||||
#endif
|
||||
/* Sway extensions */
|
||||
case 100: /* IPC_GET_INPUTS */
|
||||
case 100: /* IPC_GET_INPUTS */
|
||||
pkt_handler = cbs->reply_inputs;
|
||||
break;
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
case I3_IPC_EVENT_WORKSPACE:
|
||||
pkt_handler = cbs->event_workspace;
|
||||
|
@ -295,10 +280,12 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
pkt_handler = cbs->event_tick;
|
||||
break;
|
||||
|
||||
/* Sway extensions */
|
||||
case ((1<<31) | 21): /* IPC_EVENT_INPUT */
|
||||
/* Sway extensions */
|
||||
#define SWAY_IPC_EVENT_INPUT ((1u << 31) | 21)
|
||||
case SWAY_IPC_EVENT_INPUT:
|
||||
pkt_handler = cbs->event_input;
|
||||
break;
|
||||
#undef SWAY_IPC_EVENT_INPUT
|
||||
|
||||
default:
|
||||
LOG_ERR("unimplemented IPC reply type: %d", hdr->type);
|
||||
|
@ -307,7 +294,7 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
}
|
||||
|
||||
if (pkt_handler != NULL)
|
||||
err = !pkt_handler(hdr->type, json, data);
|
||||
err = !pkt_handler(sock, hdr->type, json, data);
|
||||
else
|
||||
LOG_DBG("no handler for reply/event %d; ignoring", hdr->type);
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <json-c/json_util.h>
|
||||
|
@ -11,7 +11,7 @@
|
|||
bool i3_get_socket_address(struct sockaddr_un *addr);
|
||||
bool i3_send_pkg(int sock, int cmd, char *data);
|
||||
|
||||
typedef bool (*i3_ipc_callback_t)(int type, const struct json_object *json, void *data);
|
||||
typedef bool (*i3_ipc_callback_t)(int sock, int type, const struct json_object *json, void *data);
|
||||
|
||||
struct i3_ipc_callbacks {
|
||||
void (*burst_done)(void *data);
|
||||
|
@ -43,6 +43,4 @@ struct i3_ipc_callbacks {
|
|||
i3_ipc_callback_t event_input;
|
||||
};
|
||||
|
||||
bool i3_receive_loop(
|
||||
int abort_fd, int sock,
|
||||
const struct i3_ipc_callbacks *callbacks, void *data);
|
||||
bool i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *callbacks, void *data);
|
||||
|
|
701
modules/i3.c
701
modules/i3.c
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,14 @@
|
|||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../module.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct particle *label;
|
||||
};
|
||||
struct private { struct particle *label; };
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
|
@ -21,6 +19,12 @@ destroy(struct module *mod)
|
|||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "label";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
|
@ -45,6 +49,7 @@ label_new(struct particle *label)
|
|||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
|
200
modules/mem.c
Normal file
200
modules/mem.c
Normal file
|
@ -0,0 +1,200 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_MODULE "mem"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
static const long min_poll_interval = 250;
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
uint16_t interval;
|
||||
uint64_t mem_free;
|
||||
uint64_t mem_total;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->label->destroy(m->label);
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "mem";
|
||||
}
|
||||
|
||||
static bool
|
||||
get_mem_stats(uint64_t *mem_free, uint64_t *mem_total)
|
||||
{
|
||||
bool mem_total_found = false;
|
||||
bool mem_free_found = false;
|
||||
|
||||
FILE *fp = NULL;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read = 0;
|
||||
|
||||
fp = fopen("/proc/meminfo", "re");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/meminfo");
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
if (strncmp(line, "MemTotal:", sizeof("MemTotal:") - 1) == 0) {
|
||||
read = sscanf(line + sizeof("MemTotal:") - 1, "%" SCNu64, mem_total);
|
||||
mem_total_found = (read == 1);
|
||||
}
|
||||
if (strncmp(line, "MemAvailable:", sizeof("MemAvailable:") - 1) == 0) {
|
||||
read = sscanf(line + sizeof("MemAvailable:"), "%" SCNu64, mem_free);
|
||||
mem_free_found = (read == 1);
|
||||
}
|
||||
}
|
||||
free(line);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return mem_free_found && mem_total_found;
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *p = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const uint64_t mem_free = p->mem_free;
|
||||
const uint64_t mem_total = p->mem_total;
|
||||
const uint64_t mem_used = mem_total - mem_free;
|
||||
|
||||
double percent_used = ((double)mem_used * 100) / (mem_total + 1);
|
||||
double percent_free = ((double)mem_free * 100) / (mem_total + 1);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){tag_new_int(mod, "free", mem_free * 1024), tag_new_int(mod, "used", mem_used * 1024),
|
||||
tag_new_int(mod, "total", mem_total * 1024),
|
||||
tag_new_int_range(mod, "percent_free", round(percent_free), 0, 100),
|
||||
tag_new_int_range(mod, "percent_used", round(percent_used), 0, 100)},
|
||||
.count = 5,
|
||||
};
|
||||
|
||||
struct exposable *exposable = p->label->instantiate(p->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
mtx_unlock(&mod->lock);
|
||||
return exposable;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
struct private *p = mod->private;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, 1, p->interval);
|
||||
if (res < 0) {
|
||||
if (EINTR == errno) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_ERRNO("unable to poll abort fd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
p->mem_free = 0;
|
||||
p->mem_total = 0;
|
||||
if (!get_mem_stats(&p->mem_free, &p->mem_total)) {
|
||||
LOG_ERR("unable to retrieve the memory stats");
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
mem_new(uint16_t interval, struct particle *label)
|
||||
{
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->label = label;
|
||||
p->interval = interval;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = p;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return mem_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < min_poll_interval) {
|
||||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_mem_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_mem_iface")));
|
||||
#endif
|
|
@ -2,47 +2,214 @@ module_sdk = declare_dependency(dependencies: [pixman, threads, tllist, fcft])
|
|||
|
||||
modules = []
|
||||
|
||||
alsa = dependency('alsa')
|
||||
udev = dependency('libudev')
|
||||
json = dependency('json-c')
|
||||
mpd = dependency('libmpdclient')
|
||||
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
|
||||
# Optional deps
|
||||
alsa = dependency('alsa', required: get_option('plugin-alsa'))
|
||||
plugin_alsa_enabled = alsa.found()
|
||||
|
||||
udev_backlight = dependency('libudev', required: get_option('plugin-backlight'))
|
||||
plugin_backlight_enabled = udev_backlight.found()
|
||||
|
||||
udev_battery = dependency('libudev', required: get_option('plugin-battery'))
|
||||
plugin_battery_enabled = udev_battery.found()
|
||||
|
||||
plugin_clock_enabled = get_option('plugin-clock').allowed()
|
||||
plugin_cpu_enabled = get_option('plugin-cpu').allowed()
|
||||
plugin_disk_io_enabled = get_option('plugin-disk-io').allowed()
|
||||
plugin_dwl_enabled = get_option('plugin-dwl').allowed()
|
||||
plugin_foreign_toplevel_enabled = backend_wayland and get_option('plugin-foreign-toplevel').allowed()
|
||||
plugin_mem_enabled = get_option('plugin-mem').allowed()
|
||||
|
||||
mpd = dependency('libmpdclient', required: get_option('plugin-mpd'))
|
||||
plugin_mpd_enabled = mpd.found()
|
||||
|
||||
# DBus dependency. Used by 'mpris'
|
||||
sdbus_library = dependency('libsystemd', 'libelogind', 'basu', required: get_option('plugin-mpris'))
|
||||
plugin_mpris_enabled = sdbus_library.found()
|
||||
|
||||
json_i3 = dependency('json-c', required: get_option('plugin-i3'))
|
||||
plugin_i3_enabled = json_i3.found()
|
||||
|
||||
plugin_label_enabled = get_option('plugin-label').allowed()
|
||||
plugin_network_enabled = get_option('plugin-network').allowed()
|
||||
|
||||
pipewire = dependency('libpipewire-0.3', required: get_option('plugin-pipewire'))
|
||||
json_pipewire = dependency('json-c', required: get_option('plugin-pipewire'))
|
||||
plugin_pipewire_enabled = pipewire.found() and json_pipewire.found()
|
||||
|
||||
pulse = dependency('libpulse', required: get_option('plugin-pulse'))
|
||||
plugin_pulse_enabled = pulse.found()
|
||||
|
||||
udev_removables = dependency('libudev', required: get_option('plugin-removables'))
|
||||
plugin_removables_enabled = udev_removables.found()
|
||||
|
||||
plugin_river_enabled = backend_wayland and get_option('plugin-river').allowed()
|
||||
|
||||
plugin_script_enabled = get_option('plugin-script').allowed()
|
||||
|
||||
json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb'))
|
||||
plugin_sway_xkb_enabled = json_sway_xkb.found()
|
||||
|
||||
json_niri_language = dependency('json-c', required: get_option('plugin-niri-language'))
|
||||
plugin_niri_language_enabled = json_niri_language.found()
|
||||
|
||||
json_niri_workspaces = dependency('json-c', required: get_option('plugin-niri-workspaces'))
|
||||
plugin_niri_workspaces_enabled = json_niri_workspaces.found()
|
||||
|
||||
xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb'))
|
||||
plugin_xkb_enabled = backend_x11 and xcb_xkb.found()
|
||||
|
||||
plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed()
|
||||
|
||||
# Module name -> (source-list, dep-list)
|
||||
deps = {
|
||||
'alsa': [[], [alsa]],
|
||||
'backlight': [[], [udev]],
|
||||
'battery': [[], [udev]],
|
||||
'clock': [[], []],
|
||||
'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||
'label': [[], []],
|
||||
'mpd': [[], [mpd]],
|
||||
'network': [[], []],
|
||||
'removables': [[], [dynlist, udev]],
|
||||
'sway_xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||
}
|
||||
mod_data = {}
|
||||
|
||||
if backend_x11
|
||||
deps += {
|
||||
'xkb': [[], [xcb_stuff, xcb_xkb]],
|
||||
'xwindow': [[], [xcb_stuff]],
|
||||
}
|
||||
if plugin_alsa_enabled
|
||||
mod_data += {'alsa': [[], [m, alsa]]}
|
||||
endif
|
||||
|
||||
foreach mod, data : deps
|
||||
if plugin_backlight_enabled
|
||||
mod_data += {'backlight': [[], [m, udev_backlight]]}
|
||||
endif
|
||||
|
||||
if plugin_battery_enabled
|
||||
mod_data += {'battery': [[], [udev_battery]]}
|
||||
endif
|
||||
|
||||
if plugin_clock_enabled
|
||||
mod_data += {'clock': [[], []]}
|
||||
endif
|
||||
|
||||
if plugin_cpu_enabled
|
||||
mod_data += {'cpu': [[], [m, dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_disk_io_enabled
|
||||
mod_data += {'disk-io': [[], [dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_dwl_enabled
|
||||
mod_data += {'dwl': [[], [dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_mem_enabled
|
||||
mod_data += {'mem': [[], [m]]}
|
||||
endif
|
||||
|
||||
if plugin_mpd_enabled
|
||||
mod_data += {'mpd': [[], [mpd]]}
|
||||
endif
|
||||
|
||||
if plugin_mpris_enabled
|
||||
sdbus = declare_dependency(compile_args: ['-DHAVE_' + sdbus_library.name().to_upper()], dependencies:[sdbus_library])
|
||||
mod_data += {'mpris': [[], [sdbus]]}
|
||||
endif
|
||||
|
||||
if plugin_i3_enabled
|
||||
mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]}
|
||||
endif
|
||||
|
||||
if plugin_label_enabled
|
||||
mod_data += {'label': [[], []]}
|
||||
endif
|
||||
|
||||
if plugin_network_enabled
|
||||
mod_data += {'network': [[], [dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_pipewire_enabled
|
||||
mod_data += {'pipewire': [[], [m, pipewire, dynlist, json_pipewire]]}
|
||||
endif
|
||||
|
||||
if plugin_pulse_enabled
|
||||
mod_data += {'pulse': [[], [m, pulse]]}
|
||||
endif
|
||||
|
||||
if plugin_removables_enabled
|
||||
mod_data += {'removables': [[], [dynlist, udev_removables]]}
|
||||
endif
|
||||
|
||||
if plugin_script_enabled
|
||||
mod_data += {'script': [[], []]}
|
||||
endif
|
||||
|
||||
if plugin_sway_xkb_enabled
|
||||
mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]}
|
||||
endif
|
||||
|
||||
if plugin_niri_language_enabled
|
||||
mod_data += {'niri-language': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_language]]}
|
||||
endif
|
||||
|
||||
if plugin_niri_workspaces_enabled
|
||||
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
|
||||
endif
|
||||
|
||||
if plugin_xkb_enabled
|
||||
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
|
||||
endif
|
||||
|
||||
if plugin_xwindow_enabled
|
||||
mod_data += {'xwindow': [[], [xcb_stuff]]}
|
||||
endif
|
||||
|
||||
if plugin_river_enabled
|
||||
river_proto_headers = []
|
||||
river_proto_src = []
|
||||
|
||||
foreach prot : ['../external/river-status-unstable-v1.xml']
|
||||
|
||||
river_proto_headers += custom_target(
|
||||
prot.underscorify() + '-client-header',
|
||||
output: '@BASENAME@.h',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'])
|
||||
|
||||
river_proto_src += custom_target(
|
||||
prot.underscorify() + '-private-code',
|
||||
output: '@BASENAME@.c',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||
endforeach
|
||||
|
||||
mod_data += {'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist, wayland_client]]}
|
||||
endif
|
||||
|
||||
if plugin_foreign_toplevel_enabled
|
||||
ftop_proto_headers = []
|
||||
ftop_proto_src = []
|
||||
|
||||
foreach prot : ['../external/wlr-foreign-toplevel-management-unstable-v1.xml']
|
||||
|
||||
ftop_proto_headers += custom_target(
|
||||
prot.underscorify() + '-client-header',
|
||||
output: '@BASENAME@.h',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'])
|
||||
|
||||
ftop_proto_src += custom_target(
|
||||
prot.underscorify() + '-private-code',
|
||||
output: '@BASENAME@.c',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||
endforeach
|
||||
|
||||
mod_data += {'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [m, dynlist, wayland_client]]}
|
||||
endif
|
||||
|
||||
foreach mod, data : mod_data
|
||||
sources = data[0]
|
||||
dep = data[1]
|
||||
deps = data[1]
|
||||
|
||||
if plugs_as_libs
|
||||
shared_module(mod, '@0@.c'.format(mod), sources,
|
||||
dependencies: [module_sdk] + dep,
|
||||
dependencies: [module_sdk] + deps,
|
||||
name_prefix: 'module_',
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('libdir'), 'yambar'))
|
||||
else
|
||||
modules += [declare_dependency(
|
||||
sources: ['@0@.c'.format(mod)] + sources,
|
||||
dependencies: [module_sdk] + dep,
|
||||
compile_args: '-DHAVE_PLUGIN_@0@'.format(mod))]
|
||||
dependencies: [module_sdk] + deps,
|
||||
compile_args: '-DHAVE_PLUGIN_@0@'.format(mod.underscorify()))]
|
||||
endif
|
||||
endforeach
|
||||
|
|
223
modules/mpd.c
223
modules/mpd.c
|
@ -1,53 +1,50 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <libgen.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <mpd/client.h>
|
||||
|
||||
#define LOG_MODULE "mpd"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
enum state {
|
||||
STATE_OFFLINE = 1000,
|
||||
STATE_STOP = MPD_STATE_STOP,
|
||||
STATE_PAUSE = MPD_STATE_PAUSE,
|
||||
STATE_PLAY = MPD_STATE_PLAY,
|
||||
};
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
char *host;
|
||||
uint16_t port;
|
||||
struct particle *label;
|
||||
|
||||
struct mpd_connection *conn;
|
||||
|
||||
enum state state;
|
||||
enum mpd_state state;
|
||||
bool repeat;
|
||||
bool random;
|
||||
bool consume;
|
||||
bool single;
|
||||
int volume;
|
||||
char *album;
|
||||
char *artist;
|
||||
char *title;
|
||||
char *file;
|
||||
|
||||
struct {
|
||||
uint64_t value;
|
||||
|
@ -65,11 +62,9 @@ destroy(struct module *mod)
|
|||
struct private *m = mod->private;
|
||||
if (m->refresh_thread_id != 0) {
|
||||
assert(m->refresh_abort_fd != -1);
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
LOG_ERRNO("failed to signal abort to refresher thread");
|
||||
} else{
|
||||
} else {
|
||||
int res;
|
||||
thrd_join(m->refresh_thread_id, &res);
|
||||
}
|
||||
|
@ -81,6 +76,7 @@ destroy(struct module *mod)
|
|||
free(m->album);
|
||||
free(m->artist);
|
||||
free(m->title);
|
||||
free(m->file);
|
||||
assert(m->conn == NULL);
|
||||
|
||||
m->label->destroy(m->label);
|
||||
|
@ -89,6 +85,12 @@ destroy(struct module *mod)
|
|||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "mpd";
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
timespec_diff_milli_seconds(const struct timespec *a, const struct timespec *b)
|
||||
{
|
||||
|
@ -127,15 +129,14 @@ content(struct module *mod)
|
|||
/* Calculate what 'elapsed' is now */
|
||||
uint64_t elapsed = m->elapsed.value;
|
||||
|
||||
if (m->state == STATE_PLAY) {
|
||||
if (m->state == MPD_STATE_PLAY) {
|
||||
elapsed += timespec_diff_milli_seconds(&now, &m->elapsed.when);
|
||||
if (elapsed > m->duration) {
|
||||
LOG_DBG(
|
||||
"dynamic update of elapsed overflowed: "
|
||||
"elapsed=%"PRIu64", duration=%"PRIu64, elapsed, m->duration);
|
||||
LOG_DBG("dynamic update of elapsed overflowed: "
|
||||
"elapsed=%" PRIu64 ", duration=%" PRIu64,
|
||||
elapsed, m->duration);
|
||||
elapsed = m->duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unsigned elapsed_secs = elapsed / 1000;
|
||||
|
@ -148,16 +149,27 @@ content(struct module *mod)
|
|||
|
||||
/* State as string */
|
||||
const char *state_str = NULL;
|
||||
switch (m->state) {
|
||||
case STATE_OFFLINE: state_str = "offline"; break;
|
||||
case STATE_STOP: state_str = "stopped"; break;
|
||||
case STATE_PAUSE: state_str = "paused"; break;
|
||||
case STATE_PLAY: state_str = "playing"; break;
|
||||
if (m->conn == NULL)
|
||||
state_str = "offline";
|
||||
else {
|
||||
switch (m->state) {
|
||||
case MPD_STATE_UNKNOWN:
|
||||
state_str = "unknown";
|
||||
break;
|
||||
case MPD_STATE_STOP:
|
||||
state_str = "stopped";
|
||||
break;
|
||||
case MPD_STATE_PAUSE:
|
||||
state_str = "paused";
|
||||
break;
|
||||
case MPD_STATE_PLAY:
|
||||
state_str = "playing";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tell particle to real-time track? */
|
||||
enum tag_realtime_unit realtime = m->state == STATE_PLAY
|
||||
? TAG_REALTIME_MSECS : TAG_REALTIME_NONE;
|
||||
enum tag_realtime_unit realtime = m->state == MPD_STATE_PLAY ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE;
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
|
@ -165,16 +177,19 @@ content(struct module *mod)
|
|||
tag_new_bool(mod, "repeat", m->repeat),
|
||||
tag_new_bool(mod, "random", m->random),
|
||||
tag_new_bool(mod, "consume", m->consume),
|
||||
tag_new_bool(mod, "single", m->single),
|
||||
tag_new_int_range(mod, "volume", m->volume, 0, 100),
|
||||
tag_new_string(mod, "album", m->album),
|
||||
tag_new_string(mod, "artist", m->artist),
|
||||
tag_new_string(mod, "title", m->title),
|
||||
tag_new_string(mod, "file", m->file),
|
||||
tag_new_string(mod, "pos", pos),
|
||||
tag_new_string(mod, "end", end),
|
||||
tag_new_int(mod, "duration", m->duration),
|
||||
tag_new_int_realtime(
|
||||
mod, "elapsed", elapsed, 0, m->duration, realtime),
|
||||
},
|
||||
.count = 11,
|
||||
.count = 14,
|
||||
};
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
@ -218,7 +233,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
struct stat st;
|
||||
if (stat(m->host, &st) == 0 && S_ISSOCK(st.st_mode)) {
|
||||
|
||||
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
|
||||
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||
strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1);
|
||||
|
@ -229,8 +244,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
LOG_DBG("%s: already exists, and is connectable", m->host);
|
||||
have_mpd_socket = true;
|
||||
} else {
|
||||
LOG_DBG("%s: already exists, but isn't connectable: %s",
|
||||
m->host, strerror(errno));
|
||||
LOG_DBG("%s: already exists, but isn't connectable: %s", m->host, strerror(errno));
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
@ -241,12 +255,15 @@ wait_for_socket_create(const struct module *mod)
|
|||
|
||||
bool ret = false;
|
||||
while (!have_mpd_socket) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = fd, .events = POLLIN}
|
||||
};
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}};
|
||||
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = true;
|
||||
|
@ -258,7 +275,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
char buf[1024];
|
||||
ssize_t len = read(fd, buf, sizeof(buf));
|
||||
|
||||
for (const char *ptr = buf; ptr < buf + len; ) {
|
||||
for (const char *ptr = buf; ptr < buf + len;) {
|
||||
const struct inotify_event *e = (const struct inotify_event *)ptr;
|
||||
LOG_DBG("inotify: CREATED: %s/%.*s", directory, e->len, e->name);
|
||||
|
||||
|
@ -268,7 +285,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
break;
|
||||
}
|
||||
|
||||
ptr += sizeof(*e) + e->len;
|
||||
ptr += sizeof(*e) + e->len;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,8 +308,7 @@ connect_to_mpd(const struct module *mod)
|
|||
|
||||
enum mpd_error merr = mpd_connection_get_error(conn);
|
||||
if (merr != MPD_ERROR_SUCCESS) {
|
||||
LOG_WARN("failed to connect to MPD: %s",
|
||||
mpd_connection_get_error_message(conn));
|
||||
LOG_WARN("failed to connect to MPD: %s", mpd_connection_get_error_message(conn));
|
||||
mpd_connection_free(conn);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -310,8 +326,7 @@ update_status(struct module *mod)
|
|||
|
||||
struct mpd_status *status = mpd_run_status(m->conn);
|
||||
if (status == NULL) {
|
||||
LOG_ERR("failed to get status: %s",
|
||||
mpd_connection_get_error_message(m->conn));
|
||||
LOG_ERR("failed to get status: %s", mpd_connection_get_error_message(m->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -323,6 +338,8 @@ update_status(struct module *mod)
|
|||
m->repeat = mpd_status_get_repeat(status);
|
||||
m->random = mpd_status_get_random(status);
|
||||
m->consume = mpd_status_get_consume(status);
|
||||
m->single = mpd_status_get_single_state(status) == MPD_SINGLE_ONESHOT;
|
||||
m->volume = mpd_status_get_volume(status);
|
||||
m->duration = mpd_status_get_total_time(status) * 1000;
|
||||
m->elapsed.value = mpd_status_get_elapsed_ms(status);
|
||||
m->elapsed.when = now;
|
||||
|
@ -332,30 +349,37 @@ update_status(struct module *mod)
|
|||
|
||||
struct mpd_song *song = mpd_run_current_song(m->conn);
|
||||
if (song == NULL && mpd_connection_get_error(m->conn) != MPD_ERROR_SUCCESS) {
|
||||
LOG_ERR("failed to get current song: %s",
|
||||
mpd_connection_get_error_message(m->conn));
|
||||
LOG_ERR("failed to get current song: %s", mpd_connection_get_error_message(m->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (song == NULL) {
|
||||
mtx_lock(&mod->lock);
|
||||
free(m->album); m->album = NULL;
|
||||
free(m->artist); m->artist = NULL;
|
||||
free(m->title); m->title = NULL;
|
||||
free(m->album);
|
||||
m->album = NULL;
|
||||
free(m->artist);
|
||||
m->artist = NULL;
|
||||
free(m->title);
|
||||
m->title = NULL;
|
||||
free(m->file);
|
||||
m->file = NULL;
|
||||
mtx_unlock(&mod->lock);
|
||||
} else {
|
||||
const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
|
||||
const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
|
||||
const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
|
||||
const char *file = mpd_song_get_uri(song);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
free(m->album);
|
||||
free(m->artist);
|
||||
free(m->title);
|
||||
free(m->file);
|
||||
|
||||
m->album = album != NULL ? strdup(album) : NULL;
|
||||
m->artist = artist != NULL ? strdup(artist) : NULL;
|
||||
m->title = title != NULL ? strdup(title) : NULL;
|
||||
m->file = file != NULL ? strdup(file) : NULL;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
mpd_song_free(song);
|
||||
|
@ -371,8 +395,9 @@ run(struct module *mod)
|
|||
struct private *m = mod->private;
|
||||
|
||||
bool aborted = false;
|
||||
int ret = 0;
|
||||
|
||||
while (!aborted) {
|
||||
while (!aborted && ret == 0) {
|
||||
|
||||
if (m->conn != NULL) {
|
||||
mpd_connection_free(m->conn);
|
||||
|
@ -381,16 +406,21 @@ run(struct module *mod)
|
|||
|
||||
/* Reset state */
|
||||
mtx_lock(&mod->lock);
|
||||
free(m->album); m->album = NULL;
|
||||
free(m->artist); m->artist = NULL;
|
||||
free(m->title); m->title = NULL;
|
||||
m->state = STATE_OFFLINE;
|
||||
free(m->album);
|
||||
m->album = NULL;
|
||||
free(m->artist);
|
||||
m->artist = NULL;
|
||||
free(m->title);
|
||||
m->title = NULL;
|
||||
free(m->file);
|
||||
m->file = NULL;
|
||||
m->state = MPD_STATE_UNKNOWN;
|
||||
m->elapsed.value = m->duration = 0;
|
||||
m->elapsed.when.tv_sec = m->elapsed.when.tv_nsec = 0;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
/* Keep trying to connect, until we succeed */
|
||||
while (!aborted) {
|
||||
while (!aborted && ret == 0) {
|
||||
if (m->port == 0) {
|
||||
/* Use inotify to watch for socket creation */
|
||||
aborted = wait_for_socket_create(mod);
|
||||
|
@ -408,16 +438,33 @@ run(struct module *mod)
|
|||
* host), wait for a while until we try to re-connect
|
||||
* again.
|
||||
*/
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
int res = poll(fds, 1, 10 * 1000);
|
||||
while (!aborted) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 2 * 1000);
|
||||
|
||||
if (res < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
else if (res == 1) {
|
||||
assert(fds[0].revents & POLLIN);
|
||||
aborted = true;
|
||||
}
|
||||
|
||||
if (res == 1) {
|
||||
assert(fds[0].revents & POLLIN);
|
||||
aborted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (aborted)
|
||||
if (aborted || ret != 0)
|
||||
break;
|
||||
|
||||
/* Initial state (after establishing a connection) */
|
||||
|
@ -435,12 +482,18 @@ run(struct module *mod)
|
|||
};
|
||||
|
||||
if (!mpd_send_idle(m->conn)) {
|
||||
LOG_ERR("failed to send IDLE command: %s",
|
||||
mpd_connection_get_error_message(m->conn));
|
||||
LOG_ERR("failed to send IDLE command: %s", mpd_connection_get_error_message(m->conn));
|
||||
break;
|
||||
}
|
||||
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
aborted = true;
|
||||
|
@ -453,8 +506,7 @@ run(struct module *mod)
|
|||
}
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
enum mpd_idle idle __attribute__ ((unused)) =
|
||||
mpd_recv_idle(m->conn, true);
|
||||
enum mpd_idle idle __attribute__((unused)) = mpd_recv_idle(m->conn, true);
|
||||
|
||||
LOG_DBG("IDLE mask: %d", idle);
|
||||
|
||||
|
@ -471,7 +523,7 @@ run(struct module *mod)
|
|||
m->conn = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return aborted ? 0 : ret;
|
||||
}
|
||||
|
||||
struct refresh_context {
|
||||
|
@ -526,9 +578,7 @@ refresh_in(struct module *mod, long milli_seconds)
|
|||
|
||||
/* Signal abort to thread */
|
||||
assert(m->refresh_abort_fd != -1);
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
LOG_ERRNO("failed to signal abort to refresher thread");
|
||||
return false;
|
||||
}
|
||||
|
@ -559,7 +609,7 @@ refresh_in(struct module *mod, long milli_seconds)
|
|||
/* Create thread */
|
||||
int r = thrd_create(&m->refresh_thread_id, &refresh_in_thread, ctx);
|
||||
|
||||
if (r != 0) {
|
||||
if (r != thrd_success) {
|
||||
LOG_ERR("failed to create refresh thread");
|
||||
close(m->refresh_abort_fd);
|
||||
m->refresh_abort_fd = -1;
|
||||
|
@ -568,7 +618,7 @@ refresh_in(struct module *mod, long milli_seconds)
|
|||
}
|
||||
|
||||
/* Detach - we don't want to have to thrd_join() it */
|
||||
//thrd_detach(tid);
|
||||
// thrd_detach(tid);
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
|
@ -579,7 +629,7 @@ mpd_new(const char *host, uint16_t port, struct particle *label)
|
|||
priv->host = strdup(host);
|
||||
priv->port = port;
|
||||
priv->label = label;
|
||||
priv->state = STATE_OFFLINE;
|
||||
priv->state = MPD_STATE_UNKNOWN;
|
||||
priv->refresh_abort_fd = -1;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
|
@ -588,6 +638,7 @@ mpd_new(const char *host, uint16_t port, struct particle *label)
|
|||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->refresh_in = &refresh_in;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
@ -598,10 +649,8 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *port = yml_get_value(node, "port");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return mpd_new(
|
||||
yml_value_as_string(host),
|
||||
port != NULL ? yml_value_as_int(port) : 0,
|
||||
conf_to_particle(c, inherited));
|
||||
return mpd_new(yml_value_as_string(host), port != NULL ? yml_value_as_int(port) : 0,
|
||||
conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -609,7 +658,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"host", true, &conf_verify_string},
|
||||
{"port", false, &conf_verify_int},
|
||||
{"port", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
1100
modules/mpris.c
Normal file
1100
modules/mpris.c
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue