Compare commits
1303 commits
Author | SHA1 | Date | |
---|---|---|---|
676bbebf4c | |||
617a12f96d | |||
5053bb0f1b | |||
21060a10a0 | |||
a56b86100a | |||
fd4b940d58 | |||
2bbac991db | |||
255794cd1c | |||
2e81eed830 | |||
60bfb19378 | |||
d7d4603855 | |||
cbd0c58b4a | |||
bbc26c263c | |||
53de1b524a | |||
c3d4fce74c | |||
8cc7c9de9b | |||
3ed26c62a5 | |||
900a0710c5 | |||
2e18d71284 | |||
61123eb7a0 | |||
27be30263f | |||
559517f345 | |||
b45ff330c9 | |||
|
bfafd93643 | ||
|
c14ed1166d | ||
|
9f6c9cc7d2 | ||
|
025d3fcb51 | ||
|
05622f985a | ||
|
c4a3f488aa | ||
|
68de299763 | ||
|
85760926d7 | ||
|
3bc3c75ff4 | ||
|
d30e5c694e | ||
|
b217ac9c9e | ||
|
fcda13cac0 | ||
|
0c2123cfe4 | ||
|
9251217bb3 | ||
|
9170785ed5 | ||
|
2e7e75a27c | ||
|
d303b794f3 | ||
|
b42323013d | ||
|
8583b5123e | ||
|
b762132037 | ||
|
fded39fa81 | ||
|
9c5e30ac61 | ||
|
9e6e656fa8 | ||
|
b9e45ca994 | ||
|
37b5646d65 | ||
|
d03e6307f5 | ||
|
1471d8824b | ||
|
04a222f3c8 | ||
|
868fdbedd3 | ||
|
e1f50f4782 | ||
|
f855d5c235 | ||
|
2546dbae2e | ||
|
d27986a316 | ||
|
4df5272164 | ||
|
839d79e68f | ||
|
ee796f6589 | ||
|
9b4944c53f | ||
|
8178919e2c | ||
|
305f9cf491 | ||
|
b866ab25b6 | ||
|
775210db08 | ||
|
2ef6f84df3 | ||
|
a97e6f2f7d | ||
|
0b4ff04be5 | ||
|
7cda35c1df | ||
|
b3007dd042 | ||
|
8967eec44b | ||
|
14f19c897a | ||
|
e9696b2150 | ||
|
c0526f2775 | ||
|
6b4898017f | ||
|
bdfc4fdab4 | ||
|
a6de61b751 | ||
|
1dd39a4e43 | ||
|
592d08c082 | ||
|
cad45ecd2c | ||
|
79081ebb4f | ||
|
1b0478edd4 | ||
|
2b4e2b2c82 | ||
|
42e041ce03 | ||
|
e58afff48a | ||
|
f34e02d824 | ||
|
2e1289f778 | ||
|
b750d96a72 | ||
|
61e38c6094 | ||
|
ad8b1802f5 | ||
|
99bd2a81b6 | ||
|
93f3da1e08 | ||
|
7161ef211c | ||
|
f77f5552ae | ||
|
be332005fa | ||
|
cc883d1723 | ||
|
30362cb124 | ||
|
098f03ac52 | ||
|
ae29c1b79f | ||
|
b327162f3b | ||
|
8eb2545eed | ||
|
f0ce6a1f7f | ||
|
a6f2e6fc5e | ||
|
87a2890b48 | ||
|
6a93238bda | ||
|
79ce2167b0 | ||
|
1fef60b32c | ||
|
0bc2c6b8e1 | ||
|
07e2364f78 | ||
|
5412591a0e | ||
|
697c3310a0 | ||
|
1682a47554 | ||
|
cace02909e | ||
|
605b749e22 | ||
|
61fe7f6d3e | ||
|
e70402e92c | ||
|
88f24100ff | ||
|
a7979e7d66 | ||
|
7ae95ad6b6 | ||
|
ccf2fb3fd0 | ||
|
7ec3adfa47 | ||
|
1c19250fe5 | ||
|
38d3a6d4c4 | ||
|
0151d20451 | ||
|
acb387a685 | ||
|
e5f36053af | ||
|
d94ee9416a | ||
|
0807bfb5c3 | ||
|
c637392bd0 | ||
|
4337575557 | ||
|
a5e0b01e3b | ||
|
97a022e452 | ||
|
7a1022de46 | ||
|
01cf02c560 | ||
|
71d65fafc6 | ||
|
a6388aea49 | ||
|
46b379815b | ||
|
7e6ae7c7be | ||
|
5ddc0d84a3 | ||
|
245cacac42 | ||
|
d158fbccba | ||
|
aa86ac931f | ||
|
700f977c87 | ||
|
84a3ebf47c | ||
|
9fe4e6c347 | ||
|
a07f40d051 | ||
|
578f806504 | ||
|
5b864e4924 | ||
|
96c9989ad5 | ||
|
54d5e83909 | ||
|
e76a6e0ba3 | ||
|
754707379a | ||
|
c40f59f7be | ||
|
3f97ea6a39 | ||
|
21cbbe685d | ||
|
910b9a8963 | ||
|
cd46b9c6a6 | ||
|
2287dcab48 | ||
|
a97f46c087 | ||
|
eb11c279f6 | ||
|
025b1ec2f2 | ||
|
20bc4b3fa6 | ||
|
ca6bf2e189 | ||
|
003a6efc8e | ||
|
a1d94d4355 | ||
|
cc910f1198 | ||
|
f4bd0fba0b | ||
|
28601cf2b7 | ||
|
72a888748e | ||
|
d57ef9364a | ||
|
87764dccf0 | ||
|
3c37b666f5 | ||
|
8d0f8a4177 | ||
|
ee9885a601 | ||
|
ee81f6198e | ||
|
40041d6080 | ||
|
b90346424b | ||
|
978519e130 | ||
|
1983408e58 | ||
|
0f74b690ca | ||
|
05f0e08493 | ||
|
8d2cdebbaf | ||
|
ae04cc9897 | ||
|
b1fd18b9af | ||
|
d5d0d6a56c | ||
|
7ee9645437 | ||
|
82e55ec517 | ||
|
fed7a067ba | ||
|
2b3b9c0ca0 | ||
|
bb36f98aaa | ||
|
80efa64614 | ||
|
84dfd85396 | ||
|
f9cfede0d0 | ||
|
55f3085c90 | ||
|
124f13075d | ||
|
2ab14d9cd3 | ||
|
470f05150d | ||
|
6af47dc506 | ||
|
7a47e9e13d | ||
|
a7dba79664 | ||
|
062506f467 | ||
|
b752eb8934 | ||
|
38a5f38b43 | ||
|
f59da4c2d8 | ||
|
42ef6a3f02 | ||
|
921ddb64f0 | ||
|
a78403d3e8 | ||
|
61ebc3aea6 | ||
|
6bac2b6e34 | ||
|
6980d5e5d0 | ||
|
db13bd94a1 | ||
|
e8c493607f | ||
|
00b7929df5 | ||
|
184762ac57 | ||
|
7635fec36f | ||
|
a8a4a86350 | ||
|
a1c4b3c65e | ||
|
b5535fcdc1 | ||
|
8e85eaa018 | ||
|
e2aa039276 | ||
|
a5fbc73c44 | ||
|
39f7ad9a75 | ||
|
2fa7931783 | ||
|
b594fd4263 | ||
|
284662a0ba | ||
|
9a6e61173f | ||
|
05c28c52c7 | ||
|
a97a7fe507 | ||
|
6f137c4927 | ||
|
218bfa2235 | ||
|
6f4f163a7d | ||
|
326e2f9318 | ||
|
f462102439 | ||
|
eabf167c1f | ||
|
df9890690a | ||
|
16c4ce2ee6 | ||
|
e6f1939857 | ||
|
2d6041be5d | ||
|
b46c295827 | ||
|
9072249d5c | ||
|
3764e758a2 | ||
|
7b63efee36 | ||
|
4afb8d8636 | ||
|
7dd5914e3f | ||
|
c001f031a1 | ||
|
7fc712862c | ||
|
5c166beebf | ||
|
6b2b5217ed | ||
|
91b1b5e037 | ||
|
2e8495d5ff | ||
|
f01179290b | ||
|
221ea0d22f | ||
|
a6d2ccc666 | ||
|
b7dd47c834 | ||
|
3da0f08fcb | ||
|
c011cfb6c1 | ||
|
d20dacb2dc | ||
|
d3de79e6b4 | ||
|
771e7482d7 | ||
|
d1ae8f277f | ||
|
83d910a7ef | ||
|
f64f71b6e2 | ||
|
a48ddbb2c8 | ||
|
8501c406af | ||
|
2383cadadc | ||
|
4d3de3be04 | ||
|
82fa347f2c | ||
|
7932af712e | ||
|
d446a44f06 | ||
|
9b61eee725 | ||
|
1fc4139b7c | ||
|
69edfc42ae | ||
|
f513582d44 | ||
|
2d99bce987 | ||
|
879744e19c | ||
|
a8fabce14e | ||
|
c228ca3b12 | ||
|
82ca97c65f | ||
|
fd1eb6e790 | ||
|
b31dea19cc | ||
|
d52f713063 | ||
|
0bf91c2f15 | ||
|
3de6f9f4b9 | ||
|
e5cdabcc0f | ||
|
fbe70607be | ||
|
9cbc39e462 | ||
|
bf7aac4e6e | ||
|
7d33171749 | ||
|
eb51a3c1c7 | ||
|
c57daf65ce | ||
|
9a2e7637c9 | ||
|
07200c466b | ||
|
33d22c2637 | ||
|
950931e1b9 | ||
|
a17356ee9d | ||
|
928f8258aa | ||
|
03731136b6 | ||
|
8897c1bde5 | ||
|
283d47ff65 | ||
|
3aadab5628 | ||
|
2a77e3a85c | ||
|
b1f49f6a1e | ||
|
4784be4076 | ||
|
a1ae6d4f34 | ||
|
5c390be25c | ||
|
4f9553f7ea | ||
|
8458eef1e6 | ||
|
4c08cd812e | ||
|
80493d3bea | ||
|
c40a174463 | ||
|
8867f4f188 | ||
|
08b5386140 | ||
|
8bde6378d4 | ||
|
1089792bc6 | ||
|
30dd0f2efb | ||
|
c019c4f382 | ||
|
5d1059ba63 | ||
|
a89bc096ef | ||
|
f4ca5eaa3b | ||
|
8a50eb6f81 | ||
|
441d4f0275 | ||
|
51c3805f7f | ||
|
d430f90434 | ||
|
4b7a6a18d5 | ||
|
8991bba90e | ||
|
e590a3cf3f | ||
|
973dd6117e | ||
|
6ce761695a | ||
|
a84b4f9a65 | ||
|
26e4bdd7eb | ||
|
5ad211f862 | ||
|
6a3e4761bf | ||
|
74ecbb6ca8 | ||
|
cbd989309d | ||
|
f0ab3ef03a | ||
|
c7f58ae2a4 | ||
|
4bbe25d195 | ||
|
d94d12897d | ||
|
2cb72fcc30 | ||
|
dced20bf89 | ||
|
fdc9b78967 | ||
|
d8216a5e2c | ||
|
0fe89e13a0 | ||
|
7542a47dbc | ||
|
d89c6b1bc1 | ||
|
99bb5e99aa | ||
|
876774ce40 | ||
|
e6bb787e01 | ||
|
6b31cdb698 | ||
|
9106ec9c8f | ||
|
0dc6a95ac2 | ||
|
618a22c122 | ||
|
4007517e45 | ||
|
d67232d8cf | ||
|
1580951474 | ||
|
40de07ba2e | ||
|
b5395fe764 | ||
|
c96d119b0e | ||
|
ed5a4e61e4 | ||
|
9c463fc2b7 | ||
|
d4339f6e43 | ||
|
27194a92c8 | ||
|
05f76c0d9a | ||
|
5a1addec7f | ||
|
439b140916 | ||
|
8be9f1a05c | ||
|
b00c0ae47c | ||
|
473d2fbd14 | ||
|
5c65a8fef4 | ||
|
f98053371e | ||
|
f6dd17b383 | ||
|
98c92bb78f | ||
|
4d422ffc84 | ||
|
5a2dfc226b | ||
|
a678241a70 | ||
|
5d80a5a1a0 | ||
|
48501fa534 | ||
|
f9017c3a38 | ||
|
2100a7cfdb | ||
|
3f524ab371 | ||
|
911230c659 | ||
|
c7df1926dc | ||
|
448ab6de83 | ||
|
4987c7d3e2 | ||
|
f141b95d8f | ||
|
1232c4d960 | ||
|
80663bdbc8 | ||
|
4485b65722 | ||
|
3ff2e49e5f | ||
|
37ccbd7f4a | ||
|
447d094fe2 | ||
|
e5007a5729 | ||
|
ec71d7fbbe | ||
|
c4046d0cd2 | ||
|
51f68addcd | ||
|
4b6b4b9052 | ||
|
dfd23a44de | ||
|
902288f30d | ||
|
7f03c9ce2d | ||
|
9e20b48cee | ||
|
046b950b8a | ||
|
afeb30e40e | ||
|
9553bec7db | ||
|
1e13798c95 | ||
|
028932a560 | ||
|
fb6be007e5 | ||
|
10c169af8a | ||
|
8001ed3ada | ||
|
4a6be622a8 | ||
|
0410ac9c6b | ||
|
527d1706c2 | ||
|
7f2ce7d76e | ||
|
abcf861fcb | ||
|
10c9321c24 | ||
|
3d809eb590 | ||
|
7756eaaa31 | ||
|
e560649531 | ||
|
4187bddad6 | ||
|
65da1e2246 | ||
|
38613495f2 | ||
|
9f89e3a657 | ||
|
868502d62e | ||
|
8d88b23947 | ||
|
7d0d1455c8 | ||
|
6d1536ca80 | ||
|
0ff49ac7d5 | ||
|
32eef6b204 | ||
|
da7734d81f | ||
|
6d7934f0fe | ||
|
d4425039b9 | ||
|
618ebbeccc | ||
|
31f1f99102 | ||
|
6e3caa6f14 | ||
|
0734c970b0 | ||
|
406eadeac7 | ||
|
a27c284869 | ||
|
beca26c2bf | ||
|
45c0a382c9 | ||
|
21ded8f640 | ||
|
413abdcae7 | ||
|
5e790b7496 | ||
|
0e37d6cbf2 | ||
|
b74ebce702 | ||
|
436cea8f37 | ||
|
af95b2e276 | ||
|
960792b2e5 | ||
|
73b071edb0 | ||
|
7b1659a1b5 | ||
|
7fd4f710a1 | ||
|
a94114dd94 | ||
|
64355e5314 | ||
|
601b2115ce | ||
|
3644acce76 | ||
|
fa66873582 | ||
|
13b06793da | ||
|
9e9dff7658 | ||
|
e872897346 | ||
|
a018343af6 | ||
|
d0e309ad0f | ||
|
b598869450 | ||
|
5ff6263986 | ||
|
3937e73e7d | ||
|
e0fc98bfb0 | ||
|
47a640e610 | ||
|
a58454de97 | ||
|
21268d7d86 | ||
|
071a69b2d8 | ||
|
659a0147da | ||
|
38c4b46351 | ||
|
cd851340e2 | ||
|
1d8b261057 | ||
|
efebc8d049 | ||
|
3c08eafa4a | ||
|
2de39be731 | ||
|
a8d1254e06 | ||
|
5fb19b66da | ||
|
de01d96b91 | ||
|
f5e6bc12db | ||
|
d0ee1b06e4 | ||
|
a8b28cd0bf | ||
|
681bba4f12 | ||
|
6c08336154 | ||
|
02465ea0c2 | ||
|
08ef42834e | ||
|
0fc1782e0b | ||
|
a811c9c886 | ||
|
c5f13746b3 | ||
|
5e96b63c60 | ||
|
d1e9e7e8bb | ||
|
9b82e736a0 | ||
|
45125f39af | ||
|
3c0499ba56 | ||
|
68bd6f8ef8 | ||
|
1a7ae9ecc6 | ||
|
49edf97b89 | ||
|
1912f3053d | ||
|
bfbd94232d | ||
|
04a2ea438b | ||
|
270b3e13bf | ||
|
180a87e0c3 | ||
|
f7feb3e674 | ||
|
0a7a4150e0 | ||
|
1751e4afa2 | ||
|
284ef6b260 | ||
|
fbe5764313 | ||
|
823a57d261 | ||
|
5ec4b04a64 | ||
|
130bfc0f0b | ||
|
f2153b95a5 | ||
|
57f669a5e7 | ||
|
a253e70328 | ||
|
4df495601a | ||
|
96f8e92822 | ||
|
1bdaedb481 | ||
|
7d85ba87d5 | ||
|
9e80d4d907 | ||
|
1759c33cf3 | ||
|
806b97895e | ||
|
8a4fc40947 | ||
|
e42f09b27e | ||
|
aa6238a5c6 | ||
|
fcbb89db90 | ||
|
f37eb31f94 | ||
|
4621de95b5 | ||
|
2390dfa946 | ||
|
070fe865dd | ||
|
b79c19b616 | ||
|
3a5365a2c3 | ||
|
965e7b2453 | ||
|
7a4d4d5ab6 | ||
|
f180c7cc58 | ||
|
100568206a | ||
|
d568ef3622 | ||
|
ecf1825c00 | ||
|
e7b853c667 | ||
|
710a2cef98 | ||
|
7e7327c374 | ||
|
6c0930cfae | ||
|
1638180b6d | ||
|
7b57299cdb | ||
|
04f6a6a08f | ||
|
213c28992c | ||
|
032a651efa | ||
|
1b66e17780 | ||
|
6db8470c2b | ||
|
07f0b7e34a | ||
|
b1501a8159 | ||
|
0776592da6 | ||
|
f22095e978 | ||
|
ebeb084da0 | ||
|
0c8d682d62 | ||
|
49de0e520b | ||
|
6f6f3cedd9 | ||
|
820598b1b8 | ||
|
780c5bf3d0 | ||
|
945838dc74 | ||
|
9d5035fa10 | ||
|
c339a16365 | ||
|
58bbcf2ac5 | ||
|
22ddcf42bd | ||
|
dff187252a | ||
|
2c8dafec70 | ||
|
905f71fa52 | ||
|
362d1a5f6f | ||
|
db41792afb | ||
|
38e54a7c81 | ||
|
9b5477675c | ||
|
738a846853 | ||
|
d759ed5051 | ||
|
ad91b1c025 | ||
|
9e04e0a27b | ||
|
c77f3aa3bc | ||
|
b368cb49da | ||
|
5959d73cde | ||
|
f8d035c079 | ||
|
5de616ff89 | ||
|
72d255f0ae | ||
|
969378846f | ||
|
13a851a636 | ||
|
06afb03807 | ||
|
06d6739da4 | ||
|
3fd8c5aaa0 | ||
|
b1adc382aa | ||
|
647ad44b31 | ||
|
22d85d6d1d | ||
|
d05ff39f02 | ||
|
b14eae4d6c | ||
|
5874850bd5 | ||
|
8f57bb952d | ||
|
5f2857ad9a | ||
|
4aff0499f0 | ||
|
d358790c6a | ||
|
b39dba8867 | ||
|
c692a776b6 | ||
|
bee2586ed4 | ||
|
632eb0c450 | ||
|
6870c3ba84 | ||
|
993be61eec | ||
|
84f2fdd419 | ||
|
b7083aacce | ||
|
6c7737cdd5 | ||
|
550b594c86 | ||
|
4fe56fc00d | ||
|
6c9e89627a | ||
|
bf1cae2399 | ||
|
463bc9665b | ||
|
a008ce3e58 | ||
|
54a2fc3a41 | ||
|
548ccc5e94 | ||
|
9be1331e1b | ||
|
dc70527797 | ||
|
6025fcd2da | ||
|
7c8ddc9c87 | ||
|
72966ee37d | ||
|
df29627983 | ||
|
83a4be3bc0 | ||
|
088309df23 | ||
|
b1bb0fe690 | ||
|
0785202860 | ||
|
5ea2708f45 | ||
|
a4a622252b | ||
|
2b888325a6 | ||
|
2d6cedd8ac | ||
|
01cde70e14 | ||
|
adbba9bf9a | ||
|
0067ce83f0 | ||
|
72045b2318 | ||
|
a9f50f1b51 | ||
|
057f894d52 | ||
|
5c207df6ae | ||
|
aac2316d74 | ||
|
6b63b87c42 | ||
|
edb8eaf410 | ||
|
b4fc88b57e | ||
|
8f0fdd6b39 | ||
|
6d9d325eca | ||
|
7579a08615 | ||
|
f75f1a9f26 | ||
|
227a23fdb5 | ||
|
e006344dcc | ||
|
9136ebd321 | ||
|
6839e81342 | ||
|
134bbbd743 | ||
|
810daac16b | ||
|
6b09be1993 | ||
|
320bba97d0 | ||
|
8f3d48c0e6 | ||
|
81c5e75624 | ||
|
fc8783ee13 | ||
|
350648b0cd | ||
|
954d7545e3 | ||
|
1484109fe0 | ||
|
4925e09995 | ||
|
4ac8c2ef7a | ||
|
44b3df5827 | ||
|
7a6788dc1c | ||
|
72deb7eaf8 | ||
|
e9b917c214 | ||
|
542d841622 | ||
|
54f6035e14 | ||
|
4cbe04f0b0 | ||
|
057faa5577 | ||
|
6a8d830281 | ||
|
bdc38e7934 | ||
|
4b54d1981c | ||
|
7215a11ffe | ||
|
e1a9782458 | ||
|
0c32d13e6f | ||
|
2eee4c390c | ||
|
92ab1a3e00 | ||
|
582c828deb | ||
|
441e7d5041 | ||
|
34dadadf90 | ||
|
45d8ed23c9 | ||
|
3558176044 | ||
|
ca62f68906 | ||
|
df9a1f9a4f | ||
|
f67ef9d64a | ||
|
e17ef8614f | ||
|
79fb28512f | ||
|
e9e67ae375 | ||
|
b22747818b | ||
|
a56b3db813 | ||
|
aac604f94c | ||
|
39fa7788b4 | ||
|
b2e92d816d | ||
|
ef6ddad256 | ||
|
d7c016ba40 | ||
|
542d235c1a | ||
|
3beb86bb38 | ||
|
d7370307f2 | ||
|
74de020980 | ||
|
381869ef65 | ||
|
a1d2e1d8f9 | ||
|
6271a0b91d | ||
|
52f5315ec5 | ||
|
639c373578 | ||
|
f4a5d05c1e | ||
|
0574775995 | ||
|
ea1e1694bf | ||
|
53258a9ffd | ||
|
08240c2e82 | ||
|
9cc26c8a95 | ||
|
8258ff1bab | ||
|
e4520d19bf | ||
|
88a3350b78 | ||
|
0bdc019a0d | ||
|
19c3975301 | ||
|
900988e5ce | ||
|
3461272611 | ||
|
058089d453 | ||
|
b9754a5b3e | ||
|
e139c4211c | ||
|
1ebbfbae14 | ||
|
16269ff01e | ||
|
60cdbab76e | ||
|
a394469c0f | ||
|
ba82f5a50f | ||
|
2dff5e2de7 | ||
|
034e8eefb9 | ||
|
cb9a60668a | ||
|
747e67e1be | ||
|
ffae9c2e26 | ||
|
b7723c7fa3 | ||
|
4912fcc57b | ||
|
19fa783997 | ||
|
23d7222576 | ||
|
20866ca7c0 | ||
|
cb30b7175d | ||
|
800d603c1f | ||
|
e73b361f62 | ||
|
5e40dfb28a | ||
|
cd0d97791e | ||
|
9cadcee844 | ||
|
86fb4c38ea | ||
|
d38a21983c | ||
|
547874dafd | ||
|
a13898cbae | ||
|
55121c86d4 | ||
|
cb13fbf917 | ||
|
1c04068f89 | ||
|
b9a30ae235 | ||
|
68b1156d15 | ||
|
7f7f464193 | ||
|
590d92102e | ||
|
c2ec379c52 | ||
|
13717d096c | ||
|
c2d869278a | ||
|
4f2b283a55 | ||
|
ebd98663c6 | ||
|
f7e37ef7d7 | ||
|
8cdc84871f | ||
|
006a1d05a9 | ||
|
caf2fea04b | ||
|
3df0b7047f | ||
|
be008c0a6f | ||
|
f23566f5d0 | ||
|
f1ce5e162a | ||
|
6e7340fcb1 | ||
|
f11cea8ab4 | ||
|
47177a802f | ||
|
6af724d1d5 | ||
|
1a69213a78 | ||
|
3c73c4d343 | ||
|
3d14931cb7 | ||
|
20f9ac3ae0 | ||
|
d393da47ed | ||
|
1158c57f85 | ||
|
f944d7c489 | ||
|
8c8fef61eb | ||
|
3921bab32e | ||
|
90fbc249af | ||
|
ffd04e9710 | ||
|
de00f9bdce | ||
|
1b34993fa9 | ||
|
f5c9272291 | ||
|
5dec4fd371 | ||
|
9a380d584f | ||
|
8552240568 | ||
|
4d1210a207 | ||
|
590ea58196 | ||
|
ec0b3c377b | ||
|
239e457d93 | ||
|
83e65e77c4 | ||
|
fd43e6cdd7 | ||
|
093da246b6 | ||
|
0dc4e6c709 | ||
|
922575fcda | ||
|
e359c75603 | ||
|
2fcd277cf9 | ||
|
fbbd95b7a8 | ||
|
172781d2c4 | ||
|
f5d296df80 | ||
|
e339158aed | ||
|
a16536ad3c | ||
|
6d89cef600 | ||
|
1355a7e5d3 | ||
|
a697500491 | ||
|
f2d6f2f319 | ||
|
02a80840a1 | ||
|
ef4d87af8e | ||
|
7584366adc | ||
|
eea3c758de | ||
|
06fa453d71 | ||
|
a8df4a5f9d | ||
|
3bb2fb8247 | ||
|
bfe5d8f493 | ||
|
835ed070e5 | ||
|
1b53e7ecf2 | ||
|
bc382e67d3 | ||
|
526560ea54 | ||
|
0238d59f8b | ||
|
bb24d4371c | ||
|
583d76f220 | ||
|
0123bb83f9 | ||
|
a964abaa11 | ||
|
eef2ab3141 | ||
|
3fdd2d2be6 | ||
|
f2aa006130 | ||
|
b4f8870a95 | ||
|
8ca036bc4c | ||
|
106bb9e8c3 | ||
|
560083ce0c | ||
|
f78981b226 | ||
|
b803f385a1 | ||
|
c560c07890 | ||
|
92577e7d26 | ||
|
320827d577 | ||
|
1d25be2059 | ||
|
3ce8e2f278 | ||
|
b3200abb23 | ||
|
99e3571eee | ||
|
85ac953111 | ||
|
a8f4d3f9c6 | ||
|
9ef826b5fd | ||
|
dd4294ecfa | ||
|
21049b9b03 | ||
|
5dcf9f9c9d | ||
|
3033c6646c | ||
|
41f8f31dd8 | ||
|
7d8b659a55 | ||
|
7d7324f6ed | ||
|
e4f7019f5b | ||
|
fde51b3f79 | ||
|
e7eef7f028 | ||
|
b3e4c7503b | ||
|
879d00775b | ||
|
478ee68f89 | ||
|
45d893aaba | ||
|
f49300e568 | ||
|
806258c2ed | ||
|
60a2d82e1c | ||
|
5e7624b619 | ||
|
39f70a9fd1 | ||
|
5012e76d07 | ||
|
c25583236b | ||
|
79a52c9f42 | ||
|
8f357e4402 | ||
|
1c5a785003 | ||
|
d2f796baa9 | ||
|
5da0819610 | ||
|
456a3ec4a4 | ||
|
0b0780b213 | ||
|
0b7f61b62e | ||
|
7aa201dd30 | ||
|
98dd8ca5f7 | ||
|
49c1465b65 | ||
|
fdd239d234 | ||
|
c22f803caf | ||
|
cec1d3cbda | ||
|
05b3fcf863 | ||
|
2f98240eed | ||
|
577dd9b438 | ||
|
0a7212e10d | ||
|
e25d878097 | ||
|
e612ec11ac | ||
|
3c2ed218cf | ||
|
5fd9501ed2 | ||
|
6fc8042899 | ||
|
421d365d8d | ||
|
368d5de171 | ||
|
30c1f712a6 | ||
|
fa98bcbdd1 | ||
|
a34ab6cc20 | ||
|
9dd0eb58eb | ||
|
5402a0b16f | ||
|
4bd23950c6 | ||
|
5ee97dbb09 | ||
|
b3d70c61f1 | ||
|
ba24ba13db | ||
|
d0bb0f9b7a | ||
|
36faceef68 | ||
|
eb63318ca5 | ||
|
bac38ece5d | ||
|
9b63345b44 | ||
|
4838cf862f | ||
|
af80f4c620 | ||
|
972ada0697 | ||
|
8e71201030 | ||
|
a8ab4f9509 | ||
|
87915a34e9 | ||
|
2ab575d190 | ||
|
4daae88a7c | ||
|
f087dbcee4 | ||
|
802f6c0342 | ||
|
e7466292d2 | ||
|
cb0c987531 | ||
|
9a0505f5f2 | ||
|
aee4b37fb5 | ||
|
2a663deb7b | ||
|
859a387676 | ||
|
9c4c712931 | ||
|
7956b28463 | ||
|
13626f6b3e | ||
|
595778f7c3 | ||
|
17983d4ecc | ||
|
650942a3e3 | ||
|
289a4bad41 | ||
|
06f229a339 | ||
|
dbbc1284e8 | ||
|
f846023fbe | ||
|
39d139a51b | ||
|
a8e01f0df9 | ||
|
88e56c4c47 | ||
|
1e4955d4b8 | ||
|
b27956071f | ||
|
9cd9ff626d | ||
|
2cc463eb1a | ||
|
dc7577069f | ||
|
2ffd4bca1d | ||
|
b16579447a | ||
|
2028d51e15 | ||
|
6e5d60d3ae | ||
|
5aba8a6a36 | ||
|
359effdd85 | ||
|
c195b0398e | ||
|
cab87896f9 | ||
|
98a57c3d3b | ||
|
611bed2007 | ||
|
32444ed49f | ||
|
a943e65ed7 | ||
|
2314a7b27a | ||
|
65faf73e8d | ||
|
f1341772e5 | ||
|
f2e0d3c0ee | ||
|
fd92b249de | ||
|
7867253fdd | ||
|
28bdf4355b | ||
|
f2d411276d | ||
|
1f94eab927 | ||
|
354d723b7c | ||
|
f44ff3fd34 | ||
|
cfa1a5c896 | ||
|
b34178ee71 | ||
|
ed9278cd39 | ||
|
ec2b2a8a4b | ||
|
8e29bf8f9e | ||
|
965d2844b0 | ||
|
50449bcee4 | ||
|
081186d97d | ||
|
da1556f9dc | ||
|
aabf269742 | ||
|
e104579fe1 | ||
|
8cefafcd6d | ||
|
e63f267803 | ||
|
b36b37d94f | ||
|
37f7492d75 | ||
|
0f368cfdf6 | ||
|
48dcd6a5ae | ||
|
838ba7cd69 | ||
|
2f86ef3b45 | ||
|
80fed80847 | ||
|
99aadc9188 | ||
|
806d08ad4c | ||
|
efbd9588f7 | ||
|
fd322aac6e | ||
|
b64b5365c9 | ||
|
dcc06307d9 | ||
|
2048ac8706 | ||
|
c1ce545bcd | ||
|
2586e4e78c | ||
|
543def72ee | ||
|
b85412fb2d | ||
|
9a1aedab62 | ||
|
7ffb8f1e7a | ||
|
9a6e044218 | ||
|
940b577225 | ||
|
8d9798c8e1 | ||
|
d047e19d08 | ||
|
3a031abcc2 | ||
|
985ad2edc8 | ||
|
20cab432b9 | ||
|
810c0b68e0 | ||
|
8e9fba36c4 | ||
|
97866362d5 | ||
|
53afade88d | ||
|
f573407b11 | ||
|
3bb7cf51ec | ||
|
771eaf331e | ||
|
007b097077 | ||
|
95d9057973 | ||
|
cf32da4375 | ||
|
8278e7c5a1 | ||
|
e5131cc787 | ||
|
04487d0764 | ||
|
6ff8932c99 | ||
|
55a94ac770 | ||
|
39f2e6bfea | ||
|
967ec5892b | ||
|
f4d382eb19 | ||
|
0ea37b7b62 | ||
|
b942c617c5 | ||
|
c0166e6763 | ||
|
9617a59d48 | ||
|
3f392be4a4 | ||
|
d2417411f3 | ||
|
a9fd33f94f | ||
|
2c1b3daec9 | ||
|
1dbe4f6fcc | ||
|
fae06da446 | ||
|
3e79b95df4 | ||
|
8738ced325 | ||
|
317f1f60b5 | ||
|
be63fdc059 | ||
|
369af17b56 | ||
|
0984c5502c | ||
|
ad14ff81a7 | ||
|
c591505977 | ||
|
10a1bc18ad | ||
|
2782b13dd2 | ||
|
756c4a113a | ||
|
60de268850 | ||
|
87fb82965f | ||
|
bd27c05fc7 | ||
|
deae6ac98d | ||
|
635373e9a0 | ||
|
b29c0594c9 | ||
|
63a2881be8 | ||
|
6b0e73db9b | ||
|
968df75945 | ||
|
0a6f914232 | ||
|
2c4af853a5 | ||
|
ed67f5515e | ||
|
ad3e3704ec | ||
|
fc3e5a8bb2 | ||
|
7b26bd9bbc | ||
|
aa3d84f8ec | ||
|
626ed97fcd | ||
|
69c57836df | ||
|
d1b0d8b123 | ||
|
206cff1fb5 | ||
|
8bb47c14aa | ||
|
74c25ba2ee | ||
|
0ef6a165e4 | ||
|
3a0b4c8b38 | ||
|
92d30efec7 | ||
|
7bdbdce087 | ||
|
cb4c561091 | ||
|
c00fe79226 | ||
|
f43ef579d8 | ||
|
7ff95446ee | ||
|
2d12c083c9 | ||
|
05a3c5d8f6 | ||
|
89b69369c6 | ||
|
d683b02b40 | ||
|
99d88e1e30 | ||
|
580bb20a9f | ||
|
dc5bfd1fc6 | ||
|
d2a35f7d02 | ||
|
e270ec55ef | ||
|
15ae56d2ee | ||
|
95d046d5e8 | ||
|
7edaadffdf | ||
|
82502c5651 | ||
|
964407534f | ||
|
9c8672a01c | ||
|
d0a264da21 | ||
|
b87dcad9c2 | ||
|
bfc3832428 | ||
|
acc36a41ae | ||
|
e7b9a0e2f8 | ||
|
6fd0dbd19d | ||
|
94ef496ee6 | ||
|
06ca5024dc | ||
|
f2a62ca7c6 | ||
|
f5c1c4975f | ||
|
53e2510a4c | ||
|
0306760eb3 | ||
|
d87b18a824 | ||
|
e36694c03d | ||
|
9e32cdef5d | ||
|
b5b62c8827 | ||
|
c2eea913e8 | ||
|
f965c6b664 | ||
|
07ca5cf383 | ||
|
b9b1e65176 | ||
|
8ff7af2b24 | ||
|
f7ee2e87bb | ||
|
e4151685ff | ||
|
4b641c08c6 | ||
|
c23670adab | ||
|
0f0a745957 | ||
|
cf16cecfec | ||
|
5f2455ac79 | ||
|
4cd6444bbf | ||
|
4b402438cc | ||
|
464a165c8b | ||
|
2837a78502 | ||
|
1a5a324498 | ||
|
801eceddd2 | ||
|
de42edc6b7 | ||
|
d2c3a41db8 | ||
|
22c91e352e | ||
|
818c0edbc4 | ||
|
a2865f941d | ||
|
c998af5649 | ||
|
f46796c653 | ||
|
008f0dc4f2 | ||
|
605b3bc20b | ||
|
6b20d09d60 | ||
|
bda153720f | ||
|
ab55cd6514 | ||
|
9b50e96b21 | ||
|
55c8f6af28 | ||
|
8875e75734 | ||
|
1cab99175b | ||
|
99a6a11e3a | ||
|
20044762af | ||
|
f22561cf90 | ||
|
cde06bd33b | ||
|
be4e901e42 | ||
|
c4762d92f7 | ||
|
32e5d633f4 | ||
|
2b8eda9d72 | ||
|
c0cc1ccd75 | ||
|
f32affa563 | ||
|
23215303ca | ||
|
e653624f5a | ||
|
37cca1c3b9 | ||
|
f5052473fb | ||
|
b5c2ca6ccf | ||
|
89247d834b | ||
|
7547537c81 | ||
|
a6eb6c3a11 | ||
|
3aeec1c7e6 | ||
|
476b2000f9 | ||
|
04fc9867ac | ||
|
a43917594e | ||
|
52e5ad7b43 | ||
|
944b223f1e | ||
|
77443b7802 | ||
|
a71828f0b4 | ||
|
a7effbff78 | ||
|
5ff0269662 | ||
|
d0200b656d | ||
|
18154dd74f | ||
|
0f6dfb3f1a | ||
|
2756f8fbb3 | ||
|
658fbd2c1c | ||
|
52ca6e5a43 | ||
|
0873a58dc0 | ||
|
885cc09636 | ||
|
232dcb733e | ||
|
f262080e78 | ||
|
422a9986b4 | ||
|
251a23d2f1 | ||
|
006a729be3 | ||
|
95410e4adf | ||
|
428b627daf | ||
|
c625baa47a | ||
|
11ebcee29a | ||
|
bd7ff3c8f1 | ||
|
b66b13211e | ||
|
da14ad3f36 | ||
|
ce6fed181e | ||
|
4f87f65f71 | ||
|
95360fe522 | ||
|
2a93a001b2 | ||
|
18ea6d36d1 | ||
|
5ade8e47f0 | ||
|
d5820160dc | ||
|
908ef67109 | ||
|
9755a70578 | ||
|
e8288ef793 | ||
|
0e538a6088 | ||
|
633bbdd6ff | ||
|
9076b30325 | ||
|
f2dc5f4b40 | ||
|
6975f179fc | ||
|
665fde5399 | ||
|
d0406ffe83 | ||
|
7fb626aa2c | ||
|
2007f8d0b9 | ||
|
7aa1bc7b44 | ||
|
7ffb2d8626 | ||
|
5851b104c5 | ||
|
932d362d30 | ||
|
59dc5f8047 | ||
|
41dc387d0c | ||
|
be2864b063 | ||
|
2592069fb9 | ||
|
967521593c | ||
|
867e87913d | ||
|
7cdcde0080 | ||
|
4bc5f73ccc | ||
|
b373de4cad | ||
|
f24cd6fa83 | ||
|
cfa4d0df48 | ||
|
e6181e2d94 | ||
|
8efa101380 | ||
|
0f96f2727b | ||
|
47e8d5b82b | ||
|
b841ba3c93 | ||
|
9b96c142d5 | ||
|
c2b0c8e754 | ||
|
7c866f1476 | ||
|
c5f78ad60d | ||
|
56a6173282 | ||
|
13e512d1f1 | ||
|
5ccf60a16b | ||
|
48c21a902e | ||
|
d2e8057141 | ||
|
51faef9fd4 | ||
|
efc2e4f94e | ||
|
47950240d0 | ||
|
28b891ef37 | ||
|
0a0e39b516 | ||
|
d19eb72296 | ||
|
533b8ca0cc | ||
|
7420434358 | ||
|
b839a9eb44 | ||
|
a1aec8fff6 | ||
|
cb3482ae27 | ||
|
b99ac07ef0 | ||
|
162398c6b6 | ||
|
0cc744ed15 | ||
|
1a093a73b1 | ||
|
37e1deaa6f | ||
|
0a266b607e | ||
|
84833dc7db | ||
|
fd57af9325 | ||
|
5e673482d3 | ||
|
65186baea6 | ||
|
a69e058dd1 | ||
|
a2446a36af | ||
|
66bdfacf6f | ||
|
b2064142d0 | ||
|
3496fa622a | ||
|
30c539f1f2 | ||
|
7a1cd4b613 | ||
|
a9c1c3581a | ||
|
30d34b12e0 | ||
|
3bb857f250 | ||
|
ef75e593f7 | ||
|
38410adcb8 | ||
|
d3fc648c08 | ||
|
6143c5e5a1 | ||
|
808c46f551 | ||
|
6e5e297d93 | ||
|
a4904d998f | ||
|
c1df1686c1 | ||
|
26ae63b5ad | ||
|
f05098a120 | ||
|
0bae9e33bb | ||
|
dc8da374fb | ||
|
affeba5b8f | ||
|
99cca7002c | ||
|
5d971267db | ||
|
b157aa9fb5 | ||
|
4e2a645bd3 | ||
|
5810a12944 | ||
|
cc0139e517 | ||
|
468e30ce66 | ||
|
fca364554e | ||
|
4d34fa9261 | ||
|
9d4936b596 | ||
|
f9267f2131 | ||
|
e0df8b84e5 | ||
|
a70c82dc4c | ||
|
6adc497f68 | ||
|
3a531c359f | ||
|
74e74bb78b | ||
|
eb38e1ce96 | ||
|
1801e83322 | ||
|
854d7dc470 | ||
|
445c5a65f1 | ||
|
96c7b762b2 | ||
|
69eb1ada0d | ||
|
66537fbe05 | ||
|
b7ca5eb3a5 | ||
|
fbd44618dc | ||
|
2f78ee4beb | ||
|
7a9cb8b533 | ||
|
da792f9b7b | ||
|
fee7cf7882 | ||
|
a17cd4759c | ||
|
fed80fb470 | ||
|
acf9d6214e | ||
|
5a60a23ebd | ||
|
8a2ef5ea5d | ||
|
bd12a51bfb | ||
|
8622673114 | ||
|
f234f81aa9 | ||
|
e931bb93c6 |
474 changed files with 22345 additions and 11208 deletions
|
@ -1,16 +1,19 @@
|
|||
engines:
|
||||
version: "2"
|
||||
plugins:
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
- python
|
||||
python:
|
||||
fixme:
|
||||
enabled: true
|
||||
radon:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.py"
|
||||
exclude_paths:
|
||||
- tests/
|
||||
- thirdparty/
|
||||
config:
|
||||
python_version: 3
|
||||
threshold: "D"
|
||||
exclude_patterns:
|
||||
- "tests/"
|
||||
- "versioneer.py"
|
||||
- "bumblebee_status/_version.py"
|
||||
- "setup.py"
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
[run]
|
||||
omit =
|
||||
versioneer.*
|
||||
bumblebee_status/_version.*
|
||||
setup.*
|
||||
tests/*
|
||||
pytests/*
|
||||
*mock*
|
||||
*funcsigs*
|
||||
*pbr*
|
||||
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
|||
bumblebee/_version.py export-subst
|
6
.github/ISSUE_TEMPLATE/Bug.md
vendored
6
.github/ISSUE_TEMPLATE/Bug.md
vendored
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: Something doesn't work (as expected)
|
||||
about: Something doesn't work as expected
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
@ -9,10 +9,10 @@ assignees: ''
|
|||
|
||||
### Bug Report
|
||||
|
||||
#### Summary
|
||||
#### Description
|
||||
Affected module: <module name>
|
||||
Version used: <e.g. latest git, AUR, PIP>
|
||||
|
||||
#### Description:
|
||||
<description>
|
||||
|
||||
#### How to reproduce
|
||||
|
|
2
.github/ISSUE_TEMPLATE/Feature.md
vendored
2
.github/ISSUE_TEMPLATE/Feature.md
vendored
|
@ -10,5 +10,3 @@ assignees: ''
|
|||
### Feature Request
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
#### Summary
|
||||
<!-- Provide a summary of the feature you would like to see implemented. -->
|
||||
|
|
2
.github/PULL_REQUEST_TEMPLATE/Improvement.md
vendored
2
.github/PULL_REQUEST_TEMPLATE/Improvement.md
vendored
|
@ -6,5 +6,3 @@ about: You have some improvement to make bumblebee-status bar better?
|
|||
### Improvement
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
#### Summary
|
||||
<!-- Provide a summary of the improvement you are submitting. -->
|
||||
|
|
2
.github/PULL_REQUEST_TEMPLATE/Other.md
vendored
2
.github/PULL_REQUEST_TEMPLATE/Other.md
vendored
|
@ -3,6 +3,8 @@ name: Other
|
|||
about: You have some other ideas you want to introduce?
|
||||
---
|
||||
|
||||
### Description
|
||||
|
||||
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->
|
||||
|
||||
**What kind of change does this PR introduce?**
|
||||
|
|
29
.github/workflows/aurpublish.yml
vendored
Normal file
29
.github/workflows/aurpublish.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
name: Upload AUR Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
aur-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
- name: Create PKGBUILD
|
||||
run: |
|
||||
python ./create-pkgbuild.py > ./PKGBUILD
|
||||
- name: Publish AUR package
|
||||
uses: KSXGitHub/github-actions-deploy-aur@v2.5.0
|
||||
with:
|
||||
pkgname: bumblebee-status
|
||||
pkgbuild: ./PKGBUILD
|
||||
commit_username: ${{ secrets.AUR_USERNAME }}
|
||||
commit_email: ${{ secrets.AUR_EMAIL }}
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
commit_message: Update AUR package
|
||||
ssh_keyscan_types: rsa,dsa,ecdsa,ed25519
|
45
.github/workflows/autotest.yml
vendored
Normal file
45
.github/workflows/autotest.yml
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
name: Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, reopened, edited ]
|
||||
push:
|
||||
|
||||
env:
|
||||
CC_TEST_REPORTER_ID: 40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
- name: Install Ubuntu dependencies
|
||||
run: sudo apt-get install -y libdbus-1-dev libgit2-dev libvirt-dev taskwarrior
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U coverage pytest pytest-mock freezegun
|
||||
pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true
|
||||
pip install $(cat requirements/modules/*.txt | grep -v power | cut -d ' ' -f 1 | sort -u)
|
||||
- name: Install Code Climate dependency
|
||||
run: |
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
chmod +x ./cc-test-reporter
|
||||
./cc-test-reporter before-build
|
||||
- name: Run tests
|
||||
run: |
|
||||
coverage run --source=. -m pytest tests -v
|
||||
- name: Report coverage
|
||||
uses: paambaati/codeclimate-action@v3.2.0
|
||||
with:
|
||||
coverageCommand: coverage3 xml
|
||||
debug: true
|
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '31 0 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
*.o
|
||||
|
||||
# Vim swap files
|
||||
*swp
|
||||
*~
|
||||
|
@ -94,3 +96,6 @@ ENV/
|
|||
|
||||
# Visual studio project files
|
||||
.vscode/
|
||||
|
||||
# mypy cache
|
||||
.mypy_cache
|
||||
|
|
9
.readthedocs.yaml
Normal file
9
.readthedocs.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
version: 2
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3"
|
26
.travis.yml
26
.travis.yml
|
@ -1,26 +0,0 @@
|
|||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y task libdbus-1-dev
|
||||
install:
|
||||
- pip install i3ipc
|
||||
- pip install psutil
|
||||
- pip install netifaces
|
||||
- pip install -U coverage==4.3
|
||||
- pip install codeclimate-test-reporter
|
||||
- pip install taskw
|
||||
- pip install pytz
|
||||
- pip install tzlocal
|
||||
- pip install dbus-python
|
||||
script:
|
||||
- nosetests -v --with-coverage --cover-erase tests/
|
||||
- CODECLIMATE_REPO_TOKEN=40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf codeclimate-test-reporter
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token: 40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf
|
|
@ -2,16 +2,15 @@ Most importantly: Many thanks for considering contributing to bumblebee-status!
|
|||
|
||||
One thing I need to mention: This is a project I am working on in my (limited) spare time. I try very hard to answer bug tickets and review Pull Requests as quickly as possible, but it might take days, in some cases even weeks, until I get around to doing so. I want to give every contribution the attention it deserves. Really: I am not ignoring you, I'm simply slow :-)
|
||||
|
||||
### Filing a Bug
|
||||
### Filing a bug
|
||||
If you want to file a bug, simply open an issue and describe your problem. Things that help narrow down the problem are:
|
||||
* Steps to reproduce
|
||||
* Relevant section of the i3 configuration
|
||||
* Debug logs and console output of bumblebee-status
|
||||
- Steps to reproduce
|
||||
- Relevant section of the i3 configuration
|
||||
- Debug logs and console output of bumblebee-status
|
||||
|
||||
But even if you can't provide those, any indicator that something is not working as it should is much appreciated!
|
||||
|
||||
### Adding a new Module or Theme
|
||||
If you want to add a new module, please have a look at [How to write a new module](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-module) and [How to write a new theme](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-theme). Then simply create a Pull Request and I will review the changes as soon as possible.
|
||||
If you want to do me a *big* favour, check the Travis status for any failing unit tests. Oh - and if you happen to add unit tests, that's also something I am very grateful for!
|
||||
### Adding a new module or theme
|
||||
If you want to add a new module, please have a look at [how to write a new module](docs/development/module.rst) and [how to write a new theme](docs/development/theme.rst). Then simply create a Pull Request and I will review the changes as soon as possible.
|
||||
|
||||
Thanks for reading until here! :)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
#start with a Python 3.x container
|
||||
FROM python:3
|
||||
|
||||
#grab repository from github
|
||||
RUN git clone --recursive https://github.com/tobi-wan-kenobi/bumblebee-status.git /var/bumblebee-status
|
||||
#run the statusline with no modules or themes specified
|
||||
CMD python3 /var/bumblebee-status/bumblebee-status
|
|
@ -1,6 +1,6 @@
|
|||
include versioneer.py
|
||||
include bumblebee/_version.py
|
||||
include bumblebee_status/_version.py
|
||||
include requirements/*
|
||||
include requirements/modules/*
|
||||
include bumblebee/themes/*
|
||||
include bumblebee/themes/icons/*
|
||||
include themes/*
|
||||
include themes/icons/*
|
||||
|
|
18
Makefile
18
Makefile
|
@ -1,18 +0,0 @@
|
|||
DEBIAN_ROOT = build/debian/bumblebee-status/
|
||||
|
||||
deb:
|
||||
mkdir -p $(DEBIAN_ROOT)/DEBIAN
|
||||
mkdir -p $(DEBIAN_ROOT)/usr/share/bumblebee-status/modules
|
||||
mkdir -p $(DEBIAN_ROOT)/usr/share/bumblebee-status/themes
|
||||
cp build/debian/control $(DEBIAN_ROOT)/DEBIAN/
|
||||
cp bumblebee-status $(DEBIAN_ROOT)/usr/share/bumblebee-status/
|
||||
cp bumblebee/modules/*.py $(DEBIAN_ROOT)/usr/share/bumblebee-status/modules/
|
||||
cp -r themes/* $(DEBIAN_ROOT)/usr/share/bumblebee-status/themes
|
||||
cp LICENSE $(DEBIAN_ROOT)/usr/share/bumblebee-status/
|
||||
dpkg-deb --build $(DEBIAN_ROOT)
|
||||
cp build/debian/bumblebee-status.deb .
|
||||
|
||||
clean:
|
||||
rm -rf $(DEBIAN_ROOT)
|
||||
rm -f build/debian/*.deb
|
||||
rm -f *.deb
|
78
Modules.md
78
Modules.md
|
@ -1,78 +0,0 @@
|
|||
# Table of modules
|
||||
|Name |Description |
|
||||
|-----|------------|
|
||||
|amixer |get volume level<br><br> |
|
||||
|arch-update |Check updates to Arch Linux.<br><br>Requires the following executable:<br> * checkupdates (from pacman-contrib)<br> |
|
||||
|battery-upower |Displays battery status, remaining percentage and charging information.<br><br>Parameters:<br> * battery-upower.warning : Warning threshold in % of remaining charge (defaults to 20)<br> * battery-upower.critical : Critical threshold in % of remaining charge (defaults to 10)<br> * battery-upower.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged<br> |
|
||||
|battery |Displays battery status, remaining percentage and charging information.<br><br>Parameters:<br> * battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)<br> * battery.warning : Warning threshold in % of remaining charge (defaults to 20)<br> * battery.critical : Critical threshold in % of remaining charge (defaults to 10)<br> * battery.showdevice : If set to "true", add the device name to the widget (defaults to False)<br> * battery.decorate : If set to "false", hides additional icons (charging, etc.) (defaults to True)<br> * battery.showpowerconsumption: If set to "true", show current power consumption (defaults to False)<br> |
|
||||
|battery_all |Displays battery status, remaining percentage and charging information.<br><br>Parameters:<br> * battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)<br> * battery.warning : Warning threshold in % of remaining charge (defaults to 20)<br> * battery.critical : Critical threshold in % of remaining charge (defaults to 10)<br> * batter.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged<br> |
|
||||
|bluetooth |Displays bluetooth status (Bluez). Left mouse click launches manager app,<br>right click toggles bluetooth. Needs dbus-send to toggle bluetooth state.<br><br>Parameters:<br> * bluetooth.device : the device to read state from (default is hci0)<br> * bluetooth.manager : application to launch on click (blueman-manager)<br> * bluetooth.dbus_destination : dbus destination (defaults to org.blueman.Mechanism)<br> * bluetooth.dbus_destination_path : dbus destination path (defaults to /)<br> * bluetooth.right_click_popup : use popup menu when right-clicked (defaults to True)<br><br> |
|
||||
|brightness |Displays the brightness of a display<br><br>Parameters:<br> * brightness.step: The amount of increase/decrease on scroll in % (defaults to 2)<br> * brightness.device_path: The device path (defaults to /sys/class/backlight/intel_backlight), can contain wildcards (in this case, the first matching path will be used)<br><br> |
|
||||
|caffeine |Enable/disable automatic screen locking.<br><br>Requires the following executables:<br> * xdg-screensaver<br> * xdotool<br> * xprop (as dependency for xdotool)<br> * notify-send<br> |
|
||||
|cmus |Displays information about the current song in cmus.<br><br>Requires the following executable:<br> * cmus-remote<br><br>Parameters:<br> * cmus.format: Format string for the song information. Tag values can be put in curly brackets (i.e. {artist})<br> Additional tags:<br> * {file} - full song file name<br> * {file1} - song file name without path prefix<br> if {file} = '/foo/bar.baz', then {file1} = 'bar.baz'<br> * {file2} - song file name without path prefix and extension suffix<br> if {file} = '/foo/bar.baz', then {file2} = 'bar'<br> * cmus.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles cmus.prev, cmus.next, cmus.shuffle and cmus.repeat, and the main display with play/pause function cmus.main.<br> |
|
||||
|cpu |Displays CPU utilization across all CPUs.<br><br>Parameters:<br> * cpu.warning : Warning threshold in % of CPU usage (defaults to 70%)<br> * cpu.critical: Critical threshold in % of CPU usage (defaults to 80%)<br> * cpu.format : Format string (defaults to "{:.01f}%")<br> |
|
||||
|cpu2 |Multiwidget CPU module<br><br>Can display any combination of:<br><br> * max CPU frequency<br> * total CPU load in percents (integer value)<br> * per-core CPU load as graph - either mono or colored<br> * CPU temperature (in Celsius degrees)<br> * CPU fan speed<br><br>Requirements:<br><br> * the psutil Python module for the first three items from the list above<br> * sensors executable for the rest<br><br>Parameters:<br> * cpu2.layout: Space-separated list of widgets to add.<br> Possible widgets are:<br> * cpu2.maxfreq<br> * cpu2.cpuload<br> * cpu2.coresload<br> * cpu2.temp<br> * cpu2.fanspeed<br> * cpu2.colored: 1 for colored per core load graph, 0 for mono (default)<br> if this is set to 1, use --markup=pango<br> * cpu2.temp_pattern: pattern to look for in the output of 'sensors -u';<br> required if cpu2.temp widged is used<br> * cpu2.fan_pattern: pattern to look for in the output of 'sensors -u';<br> required if cpu2.fanspeed widged is used<br><br>Note: if you are getting "n/a" for CPU temperature / fan speed, then you're<br>lacking the aforementioned pattern settings or they have wrong values.<br><br> |
|
||||
|currency |Displays currency exchange rates. Currently, displays currency between GBP and USD/EUR only.<br><br>Requires the following python packages:<br> * requests<br><br>Parameters:<br> * currency.interval: Interval in minutes between updates, default is 1.<br> * currency.source: Source currency (ex. "GBP", "EUR"). Defaults to "auto", which infers the local one from IP address.<br> * currency.destination: Comma-separated list of destination currencies (defaults to "USD,EUR")<br> * currency.sourceformat: String format for source formatting; Defaults to "{}: {}" and has two variables,<br> the base symbol and the rate list<br> * currency.destinationdelimiter: Delimiter used for separating individual rates (defaults to "|")<br><br>Note: source and destination names right now must correspond to the names used by the API of https://markets.ft.com<br> |
|
||||
|datetime |Displays the current date and time.<br><br>Parameters:<br> * datetime.format: strftime()-compatible formatting string<br> * date.format : alias for datetime.format<br> * time.format : alias for datetime.format<br> * datetime.locale: locale to use rather than the system default<br> * date.locale : alias for datetime.locale<br> * time.locale : alias for datetime.locale<br> |
|
||||
|datetimetz |Displays the current date and time with timezone options.<br><br>Parameters:<br> * datetimetz.format : strftime()-compatible formatting string<br> * datetimetz.timezone : IANA timezone name<br> * datetz.format : alias for datetimetz.format<br> * timetz.format : alias for datetimetz.format<br> * timetz.timezone : alias for datetimetz.timezone<br> * datetimetz.locale : locale to use rather than the system default<br> * datetz.locale : alias for datetimetz.locale<br> * timetz.locale : alias for datetimetz.locale<br> * timetz.timezone : alias for datetimetz.timezone<br> |
|
||||
|deadbeef |Displays the current song being played in DeaDBeeF and provides<br>some media control bindings.<br>Left click toggles pause, scroll up skips the current song, scroll<br>down returns to the previous song.<br><br>Requires the following library:<br> * subprocess<br>Parameters:<br> * deadbeef.format: Format string (defaults to "{artist} - {title}")<br> Available values are: {artist}, {title}, {album}, {length},<br> {trackno}, {year}, {comment},<br> {copyright}, {time}<br> This is deprecated, but much simpler.<br> * deadbeef.tf_format: A foobar2000 title formatting-style format string.<br> These can be much more sophisticated than the standard<br> format strings. This is off by default, but specifying<br> any tf_format will enable it. If both deadbeef.format<br> and deadbeef.tf_format are specified, deadbeef.tf_format<br> takes priority.<br> * deadbeef.tf_format_if_stopped: Controls whether or not the tf_format format<br> string should be displayed even if no song is paused or<br> playing. This could be useful if you want to implement<br> your own stop strings with the built in logic. Any non-<br> null value will enable this (by default the module will<br> hide itself when the player is stopped).<br> * deadbeef.previous: Change binding for previous song (default is left click)<br> * deadbeef.next: Change binding for next song (default is right click)<br> * deadbeef.pause: Change binding for toggling pause (default is middle click)<br> Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are:<br> LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN<br><br> |
|
||||
|deezer |Displays the current song being played<br>Requires the following library:<br> * python-dbus<br>Parameters:<br> * deezer.format: Format string (defaults to "{artist} - {title}")<br> Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus}<br> * deezer.previous: Change binding for previous song (default is left click)<br> * deezer.next: Change binding for next song (default is right click)<br> * deezer.pause: Change binding for toggling pause (default is middle click)<br> Available options for deezer.previous, deezer.next and deezer.pause are:<br> LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN<br> |
|
||||
|disk |Shows free diskspace, total diskspace and the percentage of free disk space.<br><br>Parameters:<br> * disk.warning: Warning threshold in % of disk space (defaults to 80%)<br> * disk.critical: Critical threshold in % of disk space (defaults ot 90%)<br> * disk.path: Path to calculate disk usage from (defaults to /)<br> * disk.open: Which application / file manager to launch (default xdg-open)<br> * disk.format: Format string, tags {path}, {used}, {left}, {size} and {percent} (defaults to "{path} {used}/{size} ({percent:05.02f}%)")<br> * (deprecated) disk.showUsed: Show used space (defaults to yes)<br> * (deprecated) disk.showSize: Show total size (defaults to yes)<br> * (deprecated) disk.showPercent: Show usage percentage (defaults to yes)<br> |
|
||||
|dnf |Displays DNF package update information (\<security\>/\<bugfixes\>/\<enhancements\>/\<other\>)<br><br>Requires the following executable:<br> * dnf<br><br>Parameters:<br> * dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes)<br><br> |
|
||||
|docker_ps |Displays the number of docker containers running<br><br>Requires the following python packages:<br> * docker<br><br> |
|
||||
|dunst |Toggle dunst notifications. |
|
||||
|error |Draws an error widget.<br> |
|
||||
|getcrypto |Displays the price of a cryptocurrency.<br><br>Requires the following python packages:<br> * requests<br><br>Parameters:<br> * getcrypto.interval: Interval in seconds for updating the price, default is 120, less than that will probably get your IP banned.<br> * getcrypto.getbtc: 0 for not getting price of BTC, 1 for getting it (default).<br> * getcrypto.geteth: 0 for not getting price of ETH, 1 for getting it (default).<br> * getcrypto.getltc: 0 for not getting price of LTC, 1 for getting it (default).<br> * getcrypto.getcur: Set the currency to display the price in, usd is the default.<br> |
|
||||
|git |Print the branch and git status for the<br>currently focused window.<br><br>Requires:<br> * xcwd<br> * Python module 'pygit2'<br> |
|
||||
|github |Displays the unread GitHub notifications for a GitHub user<br><br>Requires the following library:<br> * requests<br><br>Parameters:<br> * github.token: GitHub user access token, the token needs to have the 'notifications' scope.<br> * github.interval: Interval in minutes between updates, default is 5.<br> |
|
||||
|gpmdp |Displays information about the current song in Google Play music player.<br><br>Requires the following executable:<br> * gpmdp-remote<br> |
|
||||
|hddtemp |Fetch hard drive temeperature data from a hddtemp daemon<br>that runs on localhost and default port (7634)<br> |
|
||||
|hostname |Displays the system hostname. |
|
||||
|http_status |Display HTTP status code<br><br>Parameters:<br> * http_status.label: Prefix label (optional)<br> * http_status.target: Target to retrieve the HTTP status from<br> * http_status.expect: Expected HTTP status<br> |
|
||||
|indicator |Displays the indicator status, for numlock, scrolllock and capslock <br><br>Parameters:<br> * indicator.include: Comma-separated list of interface prefixes to include (defaults to "numlock,capslock")<br> * indicator.signalstype: If you want the signali type color to be "critical" or "warning" (defaults to "warning")<br> |
|
||||
|kernel |Shows Linux kernel version information |
|
||||
|layout-xkb |Displays the current keyboard layout using libX11<br><br>Requires the following library:<br> * libX11.so.6<br>and python module:<br> * xkbgroup<br><br>Parameters:<br> * layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed)<br> * layout-xkb.show_variant: Boolean that indecates whether the variant name should be displayed. Defaults to true.<br> |
|
||||
|layout-xkbswitch |Displays and changes the current keyboard layout<br><br>Requires the following executable:<br> * xkb-switch<br> |
|
||||
|layout |Displays and changes the current keyboard layout<br><br>Requires the following executable:<br> * setxkbmap<br> |
|
||||
|load |Displays system load.<br><br>Parameters:<br> * load.warning : Warning threshold for the one-minute load average (defaults to 70% of the number of CPUs)<br> * load.critical: Critical threshold for the one-minute load average (defaults to 80% of the number of CPUs)<br> |
|
||||
|memory |Displays available RAM, total amount of RAM and percentage available.<br><br>Parameters:<br> * memory.warning : Warning threshold in % of memory used (defaults to 80%)<br> * memory.critical: Critical threshold in % of memory used (defaults to 90%)<br> * memory.format: Format string (defaults to "{used}/{total} ({percent:05.02f}%)")<br> * memory.usedonly: Only show the amount of RAM in use (defaults to False). Same as memory.format="{used}"<br> |
|
||||
|mocp |Displays information about the current song in mocp. Left click toggles play/pause. Right click toggles shuffle.<br><br>Requires the following executable:<br> * mocp<br><br>Parameters:<br> * mocp.format: Format string for the song information. Replace string sequences with the actual information:<br> %state State<br> %file File<br> %title Title, includes track, artist, song title and album<br> %artist Artist<br> %song SongTitle<br> %album Album<br> %tt TotalTime<br> %tl TimeLeft<br> %ts TotalSec<br> %ct CurrentTime<br> %cs CurrentSec<br> %b Bitrate<br> %r Sample rate<br> |
|
||||
|mpd |Displays information about the current song in mpd.<br><br>Requires the following executable:<br> * mpc<br><br>Parameters:<br> * mpd.format: Format string for the song information.<br> Supported tags (see `man mpc` for additional information)<br> * {name}<br> * {artist}<br> * {album}<br> * {albumartist}<br> * {comment}<br> * {composer}<br> * {date}<br> * {originaldate}<br> * {disc}<br> * {genre}<br> * {performer}<br> * {title}<br> * {track}<br> * {time}<br> * {file}<br> * {id}<br> * {prio}<br> * {mtime}<br> * {mdate}<br> Additional tags:<br> * {position} - position of currently playing song<br> not to be confused with %position% mpc tag<br> * {duration} - duration of currently playing song<br> * {file1} - song file name without path prefix<br> if {file} = '/foo/bar.baz', then {file1} = 'bar.baz'<br> * {file2} - song file name without path prefix and extension suffix<br> if {file} = '/foo/bar.baz', then {file2} = 'bar'<br> * mpd.host: MPD host to connect to. (mpc behaviour by default)<br> * mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main.<br> |
|
||||
|network_traffic |Displays network traffic<br> * No extra configuration needed<br> |
|
||||
|nic |Displays the name, IP address(es) and status of each available network interface.<br><br>Requires the following python module:<br> * netifaces<br><br>Parameters:<br> * nic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth,br")<br> * nic.include: Comma-separated list of interfaces to include<br> * nic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -\> show all devices that are not in state down)<br> * nic.format: Format string (defaults to "{intf} {state} {ip} {ssid}")<br> |
|
||||
|notmuch_count |Displays the result of a notmuch count query<br> default : unread emails which path do not contained "Trash" (notmuch count "tag:unread AND NOT path:/.*Trash.*/")<br><br>Parameters:<br> * notmuch_count.query: notmuch count query to show result <br><br>Errors:<br> if the notmuch query failed, the shown value is -1<br><br>Dependencies:<br> notmuch (https://notmuchmail.org/)<br> |
|
||||
|nvidiagpu |Displays GPU name, temperature and memory usage.<br><br>Parameters:<br> * nvidiagpu.format: Format string (defaults to "{name}: {temp}°C %{usedmem}/{totalmem} MiB")<br> Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem}<br><br>Requires nvidia-smi<br> |
|
||||
|pacman |Displays update information per repository for pacman.<br><br>Parameters:<br> * pacman.sum: If you prefere displaying updates with a single digit (defaults to "False")<br><br>Requires the following executables:<br> * fakeroot<br> * pacman<br> |
|
||||
|pihole |Displays the pi-hole status (up/down) together with the number of ads that were blocked today<br>Parameters:<br> * pihole.address : pi-hole address (e.q: http://192.168.1.3)<br> * pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)<br> |
|
||||
|ping |Periodically checks the RTT of a configurable host using ICMP echos<br><br>Requires the following executable:<br> * ping<br><br>Parameters:<br> * ping.interval: Time in seconds between two RTT checks (defaults to 60)<br> * ping.address : IP address to check<br> * ping.timeout : Timeout for waiting for a reply (defaults to 5.0)<br> * ping.probes : Number of probes to send (defaults to 5)<br> * ping.warning : Threshold for warning state, in seconds (defaults to 1.0)<br> * ping.critical: Threshold for critical state, in seconds (defaults to 2.0)<br> |
|
||||
|pomodoro |Display and run a Pomodoro timer.<br>Left click to start timer, left click again to pause.<br>Right click will cancel the timer.<br><br>Parameters:<br> * pomodoro.work: The work duration of timer in minutes (defaults to 25)<br> * pomodoro.break: The break duration of timer in minutes (defaults to 5)<br> * pomodoro.format: Timer display format with "%m" and "%s" for minutes and seconds (defaults to "%m:%s")<br> Examples: "%m min %s sec", "%mm", "", "timer"<br> * pomodoro.notify: Notification command to run when timer ends/starts (defaults to nothing)<br> Example: 'notify-send "Time up!"'. If you want to chain multiple commands,<br> please use an external wrapper script and invoke that. The module itself does<br> not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532<br> for a detailled explanation)<br> |
|
||||
|prime |Displays and changes the current selected prime video card<br><br>Left click will call 'sudo prime-select nvidia'<br>Right click will call 'sudo prime-select nvidia'<br><br>Running these commands without a password requires editing your sudoers file<br>(always use visudo, it's very easy to make a mistake and get locked out of your computer!)<br><br>sudo visudo -f /etc/sudoers.d/prime<br><br>Then put a line like this in there:<br><br> user ALL=(ALL) NOPASSWD: /usr/bin/prime-select<br><br>If you can't figure out the sudoers thing, then don't worry, it's still really useful.<br><br>Parameters:<br> * prime.nvidiastring: String to use when nvidia is selected (defaults to "intel")<br> * prime.intelstring: String to use when intel is selected (defaults to "intel")<br><br>Requires the following executable:<br> * prime-select<br><br> |
|
||||
|progress |<br>Show progress for cp, mv, dd, ...<br><br>Parameters:<br> * progress.placeholder: Text to display while no process is running (defaults to "n/a")<br> * progress.barwidth: Width of the progressbar if it is used (defaults to 8)<br> * progress.format: Format string (defaults to "{bar} {cmd} {arg}")<br> Available values are: {bar} {pid} {cmd} {arg} {percentage} {quantity} {speed} {time}<br> * progress.barfilledchar: Character used to draw the filled part of the bar (defaults to "#"), notice that it can be a string<br> * progress.baremptychar: Character used to draw the empty part of the bar (defaults to "-"), notice that it can be a string<br><br>Requires the following executable:<br> * progress<br> |
|
||||
|publicip |Displays public IP address<br><br>Requires the following python packages:<br> * requests<br><br>Parameters:<br> * publicip.region: us-central (default), us-east, us-west, uk, de, pl, nl<br> * publicip.service: web address that returns plaintext ip address (ex. "http://l2.io/ip")<br> |
|
||||
|pulseaudio |Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.<br><br>Aliases: pasink (use this to control output instead of input), pasource<br><br>Parameters:<br> * pulseaudio.autostart: If set to "true" (default is "false"), automatically starts the pulseaudio daemon if it is not running<br> * pulseaudio.percent_change: How much to change volume by when scrolling on the module (default is 2%)<br> * pulseaudio.limit: Upper limit for setting the volume (default is 0%, which means "no limit")<br> Note: If the left and right channels have different volumes, the limit might not be reached exactly.<br> * pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango;<br> 0 for not showing volume bars (default)<br><br>Requires the following executable:<br> * pulseaudio<br> * pactl<br> * pavucontrol<br> |
|
||||
|redshift |Displays the current color temperature of redshift<br><br>Requires the following executable:<br> * redshift<br><br>Parameters:<br> * redshift.location : location provider, either of "geoclue2" (default), "ipinfo" (requires the requests package), or "manual"<br> * redshift.lat : latitude if location is set to "manual"<br> * redshift.lon : longitude if location is set to "manual"<br> |
|
||||
|rotation |Shows a widget for each connected screen and allows the user to loop through different orientations.<br><br>Requires the following executable:<br> * xrandr<br> |
|
||||
|rss |RSS news ticker<br><br>Fetches rss news items and shows these as a news ticker.<br>Left-clicking will open the full story in a browser.<br>New stories are highlighted.<br><br>Parameters:<br> * rss.feeds : Space-separated list of RSS URLs<br> * rss.length : Maximum length of the module, default is 60<br> |
|
||||
|sensors |Displays sensor temperature<br><br>Parameters:<br> * sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp).<br> * sensors.json: if set to "true", interpret sensors.path as JSON "path" in the output<br> of "sensors -j" (i.e. \<key1\>/\<key2\>/.../\<value\>), for example, path could<br> be: "coretemp-isa-00000/Core 0/temp1_input" (defaults to "false")<br> * sensors.match: (fallback) Line to match against output of 'sensors -u' (default: temp1_input)<br> * sensors.match_pattern: (fallback) Line to match against before temperature is read (no default)<br> * sensors.match_number: (fallback) which of the matches you want (default -1: last match).<br> * sensors.show_freq: whether to show CPU frequency. (default: true)<br> |
|
||||
|sensors2 |Displays sensor temperature and CPU frequency<br><br>Parameters:<br><br> * sensors2.chip: "sensors -u" compatible filter for chip to display (default to empty - show all chips)<br> * sensors2.showcpu: Enable or disable CPU frequency display (default: true)<br> * sensors2.showtemp: Enable or disable temperature display (default: true)<br> * sensors2.showfan: Enable or disable fan display (default: true)<br> * sensors2.showother: Enable or display "other" sensor readings (default: false)<br> * sensors2.showname: Enable or disable show of sensor name (default: false)<br> * sensors2.chip_include: Comma-separated list of chip to include (defaults to "" will include all by default, example: "coretemp,bat") <br> * sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to "" will exlude none by default) <br> * sensors2.field_include: Comma separated list of chip to include (defaults to "" will include all by default, example: "temp,fan") <br> * sensors2.field_exclude: Comma separated list of chip to exclude (defaults to "" will exclude none by default) <br> * sensors2.chip_field_exclude: Comma separated list of chip field to exclude (defaults to "" will exclude none by default, example: "coretemp-isa-0000.temp1,coretemp-isa-0000.fan1") <br> * sensors2.chip_field_include: Comma-separated list of adaper field to include (defaults to "" will include all by default) <br>|
|
||||
|shell | Execute command in shell and print result<br><br>Few command examples:<br> 'ping 1.1.1.1 -c 1 | grep -Po "(?\<=time=)\d+(\.\d+)? ms"'<br> 'echo "BTC=$(curl -s rate.sx/1BTC | grep -Po "^\d+")USD"'<br> 'curl -s https://wttr.in/London?format=%l+%t+%h+%w'<br> 'pip3 freeze | wc -l'<br> 'any_custom_script.sh | grep arguments'<br><br>Parameters:<br> * shell.command: Command to execute<br> Use single parentheses if evaluating anything inside (sh-style)<br> For example shell.command='echo $(date +"%H:%M:%S")'<br> But NOT shell.command="echo $(date +'%H:%M:%S')"<br> Second one will be evaluated only once at startup<br> * shell.interval: Update interval in seconds<br> (defaults to 1s == every bumblebee-status update)<br> * shell.async: Run update in async mode. Won't run next thread if<br> previous one didn't finished yet. Useful for long<br> running scripts to avoid bumblebee-status freezes<br> (defaults to False)<br> |
|
||||
|shortcut |Shows a widget per user-defined shortcut and allows to define the behaviour<br>when clicking on it.<br><br>For more than one shortcut, the commands and labels are strings separated by<br>a demiliter (; semicolon by default).<br><br>For example in order to create two shortcuts labeled A and B with commands<br>cmdA and cmdB you could do:<br><br> ./bumblebee-status -m shortcut -p shortcut.cmd="ls;ps" shortcut.label="A;B"<br><br>Parameters:<br> * shortcut.cmds : List of commands to execute<br> * shortcut.labels: List of widgets' labels (text)<br> * shortcut.delim : Commands and labels delimiter (; semicolon by default)<br> |
|
||||
|spaceapi |Displays the state of a Space API endpoint<br>Space API is an API for hackspaces based on JSON. See spaceapi.io for<br>an example.<br><br>Requires the following libraries:<br> * requests<br> * regex<br><br>Parameters:<br> * spaceapi.url: String representation of the api endpoint<br> * spaceapi.format: Format string for the output<br><br>Format Strings:<br> * Format strings are indicated by double %%<br> * They represent a leaf in the JSON tree, layers seperated by "."<br> * Boolean values can be overwritten by appending "%true%false"<br> in the format string<br> * Example: to reference "open" in "{"state":{"open": true}}"<br> you would write "%%state.open%%", if you also want<br> to say "Open/Closed" depending on the boolean you<br> would write "%%state.open%Open%Closed%%"<br> |
|
||||
|spacer |Draws a widget with configurable text content.<br><br>Parameters:<br> * spacer.text: Widget contents (defaults to empty string)<br> |
|
||||
|spotify |Displays the current song being played<br>Requires the following library:<br> * python-dbus<br>Parameters:<br> * spotify.format: Format string (defaults to "{artist} - {title}")<br> Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus}<br> * spotify.previous: Change binding for previous song (default is left click)<br> * spotify.next: Change binding for next song (default is right click)<br> * spotify.pause: Change binding for toggling pause (default is middle click)<br> Available options for spotify.previous, spotify.next and spotify.pause are:<br> LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN<br> |
|
||||
|stock |Display a stock quote from worldtradingdata.com<br><br>Requires the following python packages:<br> * requests<br><br>Parameters:<br> * stock.symbols : Comma-separated list of symbols to fetch<br> * stock.change : Should we fetch change in stock value (defaults to True)<br> |
|
||||
|sun |Displays sunrise and sunset times<br><br>Requires the following python packages:<br> * requests<br> * suntime<br><br>Parameters:<br> * cpu.lat : Latitude of your location<br> * cpu.lon : Longitude of your location<br> |
|
||||
|system | system module<br><br>adds the possibility to<br> * shutdown<br> * reboot<br>the system.<br> <br>Per default a confirmation dialog is shown before the actual action is performed.<br> <br>Parameters:<br> * system.confirm: show confirmation dialog before performing any action (default: true) <br> * system.reboot: specify a reboot command (defaults to 'reboot')<br> * system.shutdown: specify a shutdown command (defaults to 'shutdown -h now')<br> * system.logout: specify a logout command (defaults to 'i3exit logout')<br> * system.switch_user: specify a command for switching the user (defaults to 'i3exit switch_user')<br> * system.lock: specify a command for locking the screen (defaults to 'i3exit lock')<br> * system.suspend: specify a command for suspending (defaults to 'i3exit suspend')<br> * system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate')<br> |
|
||||
|taskwarrior |Displays the number of pending tasks in TaskWarrior.<br><br>Requires the following library:<br> * taskw<br><br>Parameters:<br> * taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc)<br> |
|
||||
|test |Test module<br> |
|
||||
|title |Displays focused i3 window title.<br><br>Requirements:<br> * i3ipc<br><br>Parameters:<br> * title.max : Maximum character length for title before truncating. Defaults to 64.<br> * title.placeholder : Placeholder text to be placed if title was truncated. Defaults to "...".<br> * title.scroll : Boolean flag for scrolling title. Defaults to False<br> |
|
||||
|todo |Displays the number of todo items from a text file<br><br>Requirements:<br> * xdg-open<br><br>Parameters:<br> * todo.file: File to read TODOs from (defaults to ~/Documents/todo.txt)<br> |
|
||||
|traffic |Displays network IO for interfaces.<br><br>Parameters:<br> * traffic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth")<br> * traffic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -\> show all devices that are not in state down)<br> * traffic.showname: If set to False, hide network interface name (defaults to True)<br> * traffic.format: Format string for download/upload speeds.<br> Defaults to "{:.2f}"<br> * traffic.graphlen: Graph lenth in seconds. Positive even integer. Each<br> char shows 2 seconds. If set, enables up/down traffic<br> graphs<br> |
|
||||
|twmn |Toggle twmn notifications. |
|
||||
|uptime |Displays the system uptime. |
|
||||
|vault |Copy passwords from a password store into the clipboard (currently supports only "pass")<br><br>Many thanks to [@bbernhard](https://github.com/bbernhard) for the idea!<br><br>Parameters:<br> * vault.duration: Duration until password is cleared from clipboard (defaults to 30)<br> * vault.location: Location of the password store (defaults to ~/.password-store)<br> * vault.offx: x-axis offset of popup menu (defaults to 0)<br> * vault.offy: y-axis offset of popup menu (defaults to 0)<br> |
|
||||
|vpn | Displays the VPN profile that is currently in use.<br><br> Left click opens a popup menu that lists all available VPN profiles and allows to establish<br> a VPN connection using that profile.<br><br> Prerequisites:<br> * nmcli needs to be installed and configured properly.<br> To quickly test, whether nmcli is working correctly, type "nmcli -g NAME,TYPE,DEVICE con" which<br> lists all the connection profiles that are configured. Make sure that your VPN profile is in that list!<br><br> e.g: to import a openvpn profile via nmcli:<br> sudo nmcli connection import type openvpn file \</path/to/your/openvpn/profile.ovpn\><br> |
|
||||
|weather |Displays the temperature on the current location based on the ip<br><br>Requires the following python packages:<br> * requests<br><br>Parameters:<br> * weather.location: Set location, defaults to 'auto' for getting location from http://ipinfo.io<br> If set to a comma-separated list, left-click and right-click can be used to rotate the locations.<br> Locations should be city names or city ids.<br> * weather.unit: metric (default), kelvin, imperial<br> * weather.showcity: If set to true, show location information, otherwise hide it (defaults to true)<br> * weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false)<br> * weather.apikey: API key from http://api.openweathermap.org<br> |
|
||||
|xkcd |Opens a random xkcd comic in the browser. |
|
||||
|xrandr |Shows a widget for each connected screen and allows the user to enable/disable screens.<br><br>Parameters:<br> * xrandr.overwrite_i3config: If set to 'true', this module assembles a new i3 config<br> every time a screen is enabled or disabled by taking the file "~/.config/i3/config.template"<br> and appending a file "~/.config/i3/config.\<screen name\>" for every screen.<br> * xrandr.autoupdate: If set to 'false', does *not* invoke xrandr automatically. Instead, the<br> module will only refresh when displays are enabled or disabled (defaults to true)<br><br>Requires the following python module:<br> * (optional) i3 - if present, the need for updating the widget list is auto-detected<br><br>Requires the following executable:<br> * xrandr<br> |
|
||||
|zpool |Displays info about zpools present on the system<br><br>Parameters:<br> * zpool.list: Comma-separated list of zpools to display info for. If empty, info for all zpools<br> is displayed. (Default: "")<br> * zpool.format: Format string, tags {name}, {used}, {left}, {size}, {percentfree}, {percentuse},<br> {status}, {shortstatus}, {fragpercent}, {deduppercent} are supported.<br> (Default: "{name} {used}/{size} ({percentfree}%)")<br> * zpool.showio: Show also widgets detailing current read and write I/O (Default: true)<br> * zpool.ioformat: Format string for I/O widget, tags {ops} (operations per seconds) and {band}<br> (bandwidth) are supported. (Default: "{band}")<br> * zpool.warnfree: Warn if free space is below this percentage (Default: 10)<br> * zpool.sudo: Use sudo when calling the `zpool` binary. (Default: false)<br><br>Option `zpool.sudo` is intended for Linux users using zfsonlinux older than 0.7.0: In pre-0.7.0<br>releases of zfsonlinux regular users couldn't invoke even informative commands such as<br>`zpool list`. If this option is true, command `zpool list` is invoked with sudo. If this option<br>is used, the following (or ekvivalent) must be added to the `sudoers(5)`:<br><br>```<br>\<username/ALL\> ALL = (root) NOPASSWD: /usr/bin/zpool list<br>```<br><br>Be aware of security implications of doing this!<br> |
|
45
PKGBUILD.template
Normal file
45
PKGBUILD.template
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Maintainer: Tobias Witek <tobi@tobi-wan-kenobi.at>
|
||||
# Contributor: Daniel M. Capella <polycitizen@gmail.com>
|
||||
# Contributor: spookykidmm <https://github.com/spookykidmm>
|
||||
|
||||
pkgname=bumblebee-status
|
||||
pkgver=<PKGVERSION>
|
||||
pkgrel=1
|
||||
pkgdesc='Modular, theme-able status line generator for the i3 window manager'
|
||||
arch=('any')
|
||||
url=https://github.com/tobi-wan-kenobi/bumblebee-status
|
||||
license=('MIT')
|
||||
depends=('python' 'python-netifaces' 'python-psutil' 'python-requests')
|
||||
optdepends=('xorg-xbacklight: to display a displays brightness'
|
||||
'xorg-xset: enable/disable automatic screen locking'
|
||||
'libnotify: enable/disable automatic screen locking'
|
||||
'dnf: display DNF package update information'
|
||||
'xorg-setxkbmap: display/change the current keyboard layout'
|
||||
'redshift: display the redshifts current color'
|
||||
'pulseaudio: control pulseaudio sink/sources'
|
||||
'xorg-xrandr: enable/disable screen outputs'
|
||||
'pacman: display current status of pacman'
|
||||
'iputils: display a ping'
|
||||
'python-i3ipc: display titlebar'
|
||||
'fakeroot: dependency of the pacman module'
|
||||
'python-pytz: timezone conversion for datetimetz module'
|
||||
'python-tzlocal: retrieve system timezone for datetimetz module'
|
||||
)
|
||||
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
||||
sha512sums=('<SHA512SUM>')
|
||||
|
||||
package() {
|
||||
install -d "$pkgdir"/usr/bin \
|
||||
"$pkgdir"/usr/share/$pkgname/bumblebee_status/{core,util} \
|
||||
"$pkgdir"/usr/share/$pkgname/bumblebee_status/modules/{core,contrib} \
|
||||
"$pkgdir"/usr/share/$pkgname/themes/icons
|
||||
ln -s /usr/share/$pkgname/$pkgname "$pkgdir"/usr/bin/$pkgname
|
||||
ln -s /usr/share/$pkgname/bumblebee-ctl "$pkgdir"/usr/bin/bumblebee-ctl
|
||||
|
||||
cd $pkgname-$pkgver
|
||||
cp -a --parents $pkgname bumblebee_status/{,core/,util/,modules/core/,modules/contrib/}*.py \
|
||||
themes/{,icons/}*.json $pkgdir/usr/share/$pkgname
|
||||
cp -r bin $pkgdir/usr/share/$pkgname/
|
||||
|
||||
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
|
||||
}
|
364
README.md
364
README.md
|
@ -1,20 +1,77 @@
|
|||
# bumblebee-status
|
||||
<img src="https://github.com/kellya/bumblebee-status-icon/blob/main/img/bumblebee_status_rtl.svg" width="50" style="display:inline-block">bumblebee-status
|
||||
=====================================================
|
||||
|
||||
logo courtesy of [kellya](https://github.com/kellya) - thank you!
|
||||
|
||||
[](https://bumblebee-status.readthedocs.io/en/main/?badge=main)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml)
|
||||
|
||||
[](https://travis-ci.org/tobi-wan-kenobi/bumblebee-status)
|
||||
[](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
|
||||
[](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage)
|
||||
[](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
|
||||
[](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml)
|
||||

|
||||
|
||||
**Many, many thanks to all contributors! As of now, 57 of the modules are from various contributors (!), and only 19 from myself.**
|
||||
**Many, many thanks to all contributors! I am still amazed by and deeply grateful for how many PRs this project gets.**
|
||||
|
||||

|
||||
[Click here for a list of available modules](https://bumblebee-status.readthedocs.io/en/main/modules.html)
|
||||
|
||||

|
||||

|
||||
|
||||
bumblebee-status is a modular, theme-able status line generator for the [i3 window manager](https://i3wm.org/).
|
||||
|
||||
**How to install**
|
||||
Focus is on:
|
||||
* ease of use, sane defaults (no mandatory configuration file)
|
||||
* [easy creation of custom themes](https://bumblebee-status.readthedocs.io/en/main/development/theme.html)
|
||||
* [easy creation of custom modules](https://bumblebee-status.readthedocs.io/en/main/development/module.html)
|
||||
|
||||
I hope you like it and I appreciate any kind of feedback: bug reports, feature requests, etc. :)
|
||||
|
||||
Thanks a lot!
|
||||
|
||||
Required i3wm version: 4.12+ (in earlier versions, blocks won't have background colors)
|
||||
|
||||
Supported Python versions: 3.4, 3.5, 3.6, 3.7, 3.8, 3.9
|
||||
|
||||
Supported FontAwesome version: 4 (free version of 5 doesn't include some of the icons)
|
||||
|
||||
---
|
||||
***NOTE***
|
||||
|
||||
The default branch for this project is `main`. If you are curious why: [ZDNet:github-master-alternative](https://www.zdnet.com/article/github-to-replace-master-with-alternative-term-to-avoid-slavery-references/)
|
||||
|
||||
---
|
||||
|
||||
Example usage:
|
||||
|
||||
```
|
||||
bar {
|
||||
status_command <path>/bumblebee-status -m cpu memory battery time \
|
||||
pasink pasource -p time.format="%H:%M" -t solarized
|
||||
}
|
||||
```
|
||||
|
||||
# Documentation
|
||||
See [the docs](https://bumblebee-status.readthedocs.io) for detailed documentation.
|
||||
|
||||
See [FAQ](https://bumblebee-status.readthedocs.io/en/main/FAQ.html) for. well, FAQs.
|
||||
|
||||
Other resources:
|
||||
|
||||
* A list of [available modules](https://bumblebee-status.readthedocs.io/en/main/modules.html)
|
||||
* [How to write a module](https://bumblebee-status.readthedocs.io/en/main/development/module.html)
|
||||
* [How to write a theme](https://bumblebee-status.readthedocs.io/en/main/development/theme.html)
|
||||
|
||||
# Installation
|
||||
```
|
||||
# from git (development snapshot)
|
||||
$ git clone git://github.com/tobi-wan-kenobi/bumblebee-status
|
||||
|
||||
# from AUR:
|
||||
git clone https://aur.archlinux.org/bumblebee-status.git
|
||||
cd bumblebee-status
|
||||
|
@ -25,83 +82,21 @@ makepkg -sicr
|
|||
pip install --user bumblebee-status
|
||||
```
|
||||
|
||||
Focus is on:
|
||||
* Ease of use (no configuration files!)
|
||||
* Theme support
|
||||
* Extensibility (of course...)
|
||||
There is also a SlackBuild available here: [slackbuilds:bumblebee-status](http://slackbuilds.org/repository/14.2/desktop/bumblebee-status/) - many thanks to [@Tonus1](https://github.com/Tonus1)!
|
||||
|
||||
One thing I like in particular: You can use the mouse wheel up/down to switch workspaces forward and back everywhere throughout the bar (unless you have mapped the mouse wheel buttons to another action for a widget, in which case this doesn't work while hovering that particular widget).
|
||||
|
||||
I hope you like it and appreciate any kind of feedback: Bug reports, Feature requests, etc. :)
|
||||
|
||||
Thanks a lot!
|
||||
|
||||
Required i3wm version: 4.12+ (in earlier versions, blocks won't have background colors)
|
||||
|
||||
Supported Python versions: 2.7, 3.3, 3.4, 3.5, 3.6
|
||||
|
||||
Supported FontAwesome version: 4 (free version of 5 doesn't include some of the icons)
|
||||
|
||||
Explicitly unsupported Python versions: 3.2 (missing unicode literals)
|
||||
|
||||
:information_source: The  is required for all themes that contain icons (because that is the font that includes these icons). Please refer to your distribution's package management on how to install them, or get them from their website directly. Also, please note that Font Awesome removed some icons used by `bumblebee-status` from the free set in version 5, so if possible, stick with 4.
|
||||
|
||||
```
|
||||
# Font Awesome installation instructions
|
||||
|
||||
# Arch Linux
|
||||
$ sudo pacman -S awesome-terminal-fonts
|
||||
|
||||
# FreeBSD
|
||||
$ sudo pkg install font-awesome
|
||||
$ sudo pkg install py36-tzlocal py36-pytz py36-netifaces py36-psutil py36-requests #for dependencies
|
||||
|
||||
# Other
|
||||
# see https://github.com/gabrielelana/awesome-terminal-fonts
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
```
|
||||
bar {
|
||||
status_command <path>/bumblebee-status -m cpu memory battery time pasink pasource -p time.format="%H:%M" -t solarized
|
||||
}
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
See [the wiki](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki) for documentation.
|
||||
|
||||
See [FAQ](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/FAQ) for, well, FAQs.
|
||||
|
||||
Other resources:
|
||||
|
||||
* A list of [available modules](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/Available-Modules)
|
||||
* [How to write a theme](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-theme)
|
||||
* [How to write a module](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-module)
|
||||
|
||||
User contributions:
|
||||
|
||||
[@somospocos:bumblebee-status-contrib](https://github.com/somospocos/bumblebee-status-contrib): Collected resources and useful tricks by @somospocos
|
||||
|
||||
[@somospocos:bumblebee-bridge-dwm](https://github.com/somospocos/bumblebee-bridge-dwm): Bridge bumblebee-status output into dwm status bar
|
||||
|
||||
[@somospocos:bumblebee-bridge-dzen2](https://github.com/somospocos/bumblebee-bridge-dzen2): Bridge bumblebee-status output into dzen2
|
||||
|
||||
# Installation
|
||||
```
|
||||
$ git clone git://github.com/tobi-wan-kenobi/bumblebee-status
|
||||
```
|
||||
An ebuild, for Gentoo Linux, is available on [gallifrey overlay](https://github.com/fedeliallalinea/gallifrey/tree/master/x11-misc/bumblebee-status). Instructions for adding the overlay can be found [here](https://github.com/fedeliallalinea/gallifrey/blob/master/README.md).
|
||||
|
||||
# Dependencies
|
||||
[Available modules](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/Available-Modules) lists the dependencies (Python modules and external executables)
|
||||
[Available modules](https://bumblebee-status.readthedocs.io/en/main/modules.html) lists the dependencies (Python modules and external executables)
|
||||
for each module. If you are not using a module, you don't need the dependencies.
|
||||
|
||||
Some themes (e.g. all ‘powerline’ themes) require Font Awesome http://fontawesome.io/ and a powerline-compatible font (powerline-fonts) https://github.com/powerline/fonts
|
||||
|
||||
# Usage
|
||||
## Normal usage
|
||||
In your i3wm configuration, modify the *status_command* for your i3bar like this:
|
||||
|
||||
```
|
||||
```bash
|
||||
bar {
|
||||
status_command <path to bumblebee-status/bumblebee-status> \
|
||||
-m <list of modules> \
|
||||
|
@ -111,228 +106,45 @@ bar {
|
|||
```
|
||||
|
||||
You can retrieve a list of modules (and their parameters) and themes by entering:
|
||||
```
|
||||
```bash
|
||||
$ cd bumblebee-status
|
||||
$ ./bumblebee-status -l themes
|
||||
$ ./bumblebee-status -l modules
|
||||
```
|
||||
|
||||
Any parameter you can specify with `-p <name>=<value>`, you can alternatively specify in `~/.bumblebee-status.conf` or `~/.config/bumblebee-status.conf`. This parameters act as a **fallback**, so values specified with `-p` have priority.
|
||||
|
||||
Parameters can also be used to override theme settings, such as:
|
||||
|
||||
```
|
||||
$ ./bumblebee-status -p <module>.theme.<theme field>=<value>
|
||||
# for example, to get a spacer with a red background:
|
||||
$ ./bumblebee-status -m spacer -p spacer.theme.bg=#ff0000
|
||||
```
|
||||
|
||||
Configuration files have a format like this:
|
||||
```
|
||||
$ cat ~/.bumblebee-status.conf
|
||||
[module-parameters]
|
||||
<key> = <value>
|
||||
```
|
||||
|
||||
For example:
|
||||
```
|
||||
$ cat ~/.bumblebee-status.conf
|
||||
[module-parameters]
|
||||
github.token=abcdefabcdef12345
|
||||
```
|
||||
|
||||
To change the update interval, use:
|
||||
```
|
||||
```bash
|
||||
$ ./bumblebee-status -m <list of modules> -p interval=<interval in seconds>
|
||||
```
|
||||
|
||||
The update interval can also be changed on a per-module basis, like this:
|
||||
```bash
|
||||
$ ./bumblebee-status -m cpu memory -p cpu.interval=5s memory.interval=1m
|
||||
```
|
||||
|
||||
All modules can be given "aliases" using `<module name>:<alias>`, by which they can be parametrized, for example:
|
||||
|
||||
```bash
|
||||
$ ./bumblebee-status -m disk:root disk:home -p root.path=/ home.path=/home
|
||||
```
|
||||
|
||||
As a simple example, this is what my i3 configuration looks like:
|
||||
|
||||
```
|
||||
```bash
|
||||
bar {
|
||||
font pango:Inconsolata 10
|
||||
position top
|
||||
tray_output none
|
||||
status_command ~/.i3/bumblebee-status/bumblebee-status -m nic disk:root cpu memory battery date time pasink pasource dnf -p root.path=/ time.format="%H:%M CW %V" date.format="%a, %b %d %Y" -t solarized-powerline
|
||||
status_command ~/.i3/bumblebee-status/bumblebee-status -m nic disk:root cpu \
|
||||
memory battery date time pasink pasource dnf \
|
||||
-p root.path=/ time.format="%H:%M CW %V" date.format="%a, %b %d %Y" \
|
||||
-t solarized-powerline
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Restart i3wm and - that's it!
|
||||
|
||||
## Events
|
||||
By default, the following events are handled:
|
||||
|
||||
- Mouse-Wheel on any module moves to the next/previous i3 workspace
|
||||
- Left-click on the "disk" module opens the specified path in nautilus
|
||||
- Left-click on either "memory" or "cpu" opens gnome-system-monitor
|
||||
- Left-click on a "pulseaudio" (or pasource/pasink) module toggles the mute state
|
||||
- Right-click on a "pulseaudio" module opens pavucontrol
|
||||
- Mouse-Wheel up/down on a "pulseaudio" module raises/lowers the volume
|
||||
|
||||
By default, the Mouse-Wheel wraps for the current output. You can disable this behavior by providing the parameter `engine.workspacewrap=false` (starting with version 1.4.5). Also, you can completely disable output switching by using `engine.workspacewheel=false`.
|
||||
|
||||
You can provide your own handlers to any module by using the following "special" configuration parameters:
|
||||
|
||||
- left-click
|
||||
- right-click
|
||||
- middle-click
|
||||
- wheel-up
|
||||
- wheel-down
|
||||
For example, to execute "pavucontrol" whenever you left-click on the nic module, you could write:
|
||||
|
||||
`$ bumblebee-status -p nic.left-click="pavucontrol"`
|
||||
|
||||
In the string, you can use the following format identifiers:
|
||||
- name
|
||||
- instance
|
||||
- button
|
||||
|
||||
For example:
|
||||
|
||||
`$ bumblebee-status -p disk.left-click="nautilus {instance}"`
|
||||
|
||||
Advanced usage:
|
||||
|
||||
You can also "programmatically" trigger click events like this:
|
||||
```
|
||||
$ bumblebee-ctl --button <button> --id <ID of the widget> --module <name of the module>
|
||||
```
|
||||
|
||||
For that to work, you need to know the ID of the widget - this can be configured by adding the `<name>.id` parameter - it's a comma-separated list with one entry per widget (for multi-widget modules).
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ bumblebee-status -m cmus -p cmus.id="prev,main,next,shuffle,repeat"
|
||||
# to "simulate" a left-click on "next":
|
||||
$ bumblebee-ctl --button left-mouse --module cmus --id next
|
||||
```
|
||||
|
||||
For a full synopsis, please see `bumblebee-ctl --help`.
|
||||
|
||||
## Errors
|
||||
If errors occur, you should see them in the i3bar itself. If that does not work, or you need more information for troubleshooting, you can activate a debug log using the `-d` or `--debug` switch:
|
||||
|
||||
```
|
||||
$ ./bumblebee-status -d -m <list of modules>
|
||||
```
|
||||
|
||||
This will create a file called `~/bumblebee-status-debug.log` by default. The file name can be changed by using the `-f` or `--logfile` option.
|
||||
|
||||
### Advanced Usage
|
||||
If you want to have a minimal bar that stays out of the way, you can use the `-a` or `--autohide` switch to specify a list of module names. All those modules will only be displayed when (and as long as) their state is either warning or critical (high CPU usage, low disk space, etc.). As long as the module is in a "normal" state and does not require attention, it will remain hidden.
|
||||
Note that this parameter is specified *in addition* to `-m` (i.e. to autohide the CPU module, you would use `bumblebee-status -m cpu memory traffic -a cpu`).
|
||||
|
||||
# Required Modules
|
||||
|
||||
Modules and commandline utilities are only required for modules, the core itself has no external dependencies at all.
|
||||
|
||||
* psutil (for the modules 'cpu', 'memory', 'traffic')
|
||||
* netifaces (for the modules 'nic', 'traffic')
|
||||
* requests (for the modules 'weather', 'github', 'getcrypto', 'stock', 'currency', 'sun')
|
||||
* power (for the module 'battery')
|
||||
* dbus (for the module 'spotify', 'deezer')
|
||||
* i3ipc (for the module 'title')
|
||||
* pacman-contrib (for module 'arch-update')
|
||||
* docker (for the module 'docker_ps')
|
||||
* pytz (for the module 'datetimetz')
|
||||
* localtz (for the module 'datetimetz')
|
||||
* suntime (for the module 'sun')
|
||||
* feedparser (for the module 'rss')
|
||||
|
||||
# Required commandline utilities
|
||||
|
||||
* xset (for the module 'caffeine')
|
||||
* notify-send (for the module 'caffeine')
|
||||
* cmus-remote (for the module 'cmus')
|
||||
* dnf (for the module 'dnf')
|
||||
* gpmdp-remote (for the module 'gpmdp')
|
||||
* setxkbmap (for the module 'layout')
|
||||
* fakeroot (for the module 'pacman')
|
||||
* pacman (for the module 'pacman')
|
||||
* pactl (for the module 'pulseaudio')
|
||||
* ping (for the module 'ping')
|
||||
* redshift (for the module 'redshift')
|
||||
* xrandr (for the module 'xrandr')
|
||||
* mpc (for the module 'mpd')
|
||||
* bluez / blueman (for module 'bluetooth')
|
||||
* dbus-send (for module 'bluetooth')
|
||||
* nvidia-smi (for module 'nvidiagpu')
|
||||
* sensors (for module 'sensors', as fallback)
|
||||
* zpool (for module 'zpool')
|
||||
* progress (for module 'progress')
|
||||
* i3exit (for module 'system')
|
||||
* dunst (for module 'dunst')
|
||||
* hddtemp (for module 'hddtemp')
|
||||
|
||||
# Examples
|
||||
Here are some screenshots for all themes that currently exist:
|
||||
|
||||
:exclamation: Some themes (all 'Powerline' themes) require [Font Awesome](http://fontawesome.io/) and a powerline-compatible font ([powerline-fonts](https://github.com/powerline/fonts), for example) to display all icons correctly.
|
||||
|
||||
:exclamation: If you want to add your own theme, just drop it into `~/.config/bumblebee-status/themes/`
|
||||
|
||||
Gruvbox Powerline (`-t gruvbox-powerline`) (contributed by [@TheEdgeOfRage](https://github.com/TheEdgeOfRage)):
|
||||
|
||||

|
||||
|
||||
Gruvbox Powerline Light (`-t gruvbox-powerline-light`) (contributed by [freed00m](https://github.com/freed00m)):
|
||||
|
||||

|
||||
|
||||
Solarized Powerline (`-t solarized-powerline`):
|
||||
|
||||

|
||||
|
||||
Gruvbox (`-t gruvbox`):
|
||||
|
||||

|
||||
|
||||
Gruvbox Light (`-t gruvbox-light`) (contributed by [freed00m](https://github.com/freed00m)):
|
||||
|
||||

|
||||
|
||||
Solarized (`-t solarized`):
|
||||
|
||||

|
||||
|
||||
Powerline (`-t powerline`):
|
||||
|
||||

|
||||
|
||||
Greyish Powerline (`-t greyish-powerline`) (contributed by Joshua Bark):
|
||||
|
||||

|
||||
|
||||
Iceberg (`-t iceberg`) (contributed by [whzup](https://github.com/whzup)):
|
||||
|
||||

|
||||
|
||||
Iceberg Powerline (`-t iceberg-powerline`) (contributed by [whzup](https://github.com/whzup)):
|
||||
|
||||

|
||||
|
||||
Iceberg Dark Powerline (`-t iceberg-dark-powerline`) (contributed by [gkeep](https://github.com/gkeep)):
|
||||
|
||||

|
||||
|
||||
Iceberg Rainbow (`-t iceberg-rainbow`) (contributed by [whzup](https://github.com/whzup)):
|
||||
|
||||

|
||||
|
||||
One Dark Powerline (`-t onedark-powerline`) (contributed by [dillasyx](https://github.com/dillasyx)):
|
||||
|
||||

|
||||
|
||||
Dracula Powerline (-t dracula-powerline) (contributed by [xsteadfastx](https://github.com/xsteadfastx)):
|
||||
|
||||

|
||||
|
||||
Nord Powerline (-t nord-powerline) (contributed by [uselessthird](https://github.com/uselessthird)):
|
||||
|
||||

|
||||
|
||||
Default (nothing or `-t default`):
|
||||
|
||||

|
||||
[List of themes](https://bumblebee-status.readthedocs.io/en/main/themes.html)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
Package: bumblebee-status
|
||||
Version: 0
|
||||
Maintainer: tobi-wan-kenobi
|
||||
Architecture: all
|
||||
Description: bumblebee-status is a modular, theme-able status line generator for the i3 window manager.
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
ln -s /usr/share/bumblebee-status/bumblebee-status /usr/local/bin/bumblebee-status
|
||||
|
||||
|
|
@ -1,35 +1,53 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
import glob
|
||||
import socket
|
||||
|
||||
button = {
|
||||
'left-mouse': 1,
|
||||
'middle-mouse': 2,
|
||||
'right-mouse': 3,
|
||||
'wheel-up': 4,
|
||||
'wheel-down': 5,
|
||||
"left-mouse": 1,
|
||||
"middle-mouse": 2,
|
||||
"right-mouse": 3,
|
||||
"wheel-up": 4,
|
||||
"wheel-down": 5,
|
||||
"update": -1,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='send commands to bumblebee-status')
|
||||
parser.add_argument('-b', '--button', choices=['left-mouse', 'right-mouse', 'middle-mouse', 'wheel-up', 'wheel-down'], help='button to emulate', default='left-mouse')
|
||||
parser.add_argument('-i', '--id', help='ID of widget to trigger', required=True)
|
||||
parser.add_argument('-m', '--module', help='name of the module to trigger', required=True)
|
||||
parser = argparse.ArgumentParser(description="send commands to bumblebee-status")
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--button",
|
||||
choices=["left-mouse", "right-mouse", "middle-mouse", "wheel-up", "wheel-down", "update"],
|
||||
help="button to emulate",
|
||||
default="left-mouse",
|
||||
)
|
||||
parser.add_argument("-i", "--id", help="ID of widget to trigger")
|
||||
parser.add_argument(
|
||||
"-m", "--module", help="name of the module to trigger", required=True
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
for f in glob.glob('/tmp/.bumblebee-status.*'):
|
||||
print('accessing {}'.format(f))
|
||||
for f in glob.glob("/tmp/.bumblebee-status.*"):
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.connect(f)
|
||||
s.sendall(json.dumps({
|
||||
'name': args.module,
|
||||
'instance': args.id,
|
||||
'button': button[args.button],
|
||||
}).encode('ascii'))
|
||||
try:
|
||||
s.connect(f)
|
||||
s.sendall(
|
||||
json.dumps(
|
||||
{
|
||||
"name": args.module,
|
||||
"instance": args.id,
|
||||
"button": button[args.button],
|
||||
}
|
||||
).encode("ascii")
|
||||
)
|
||||
except Exception as e:
|
||||
os.remove(f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
227
bumblebee-status
227
bumblebee-status
|
@ -1,84 +1,177 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import json
|
||||
import time
|
||||
import signal
|
||||
import bumblebee.theme
|
||||
import bumblebee.engine
|
||||
import bumblebee.config
|
||||
import bumblebee.output
|
||||
import bumblebee.input
|
||||
import bumblebee.modules.error
|
||||
import socket
|
||||
import logging
|
||||
import threading
|
||||
|
||||
try:
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('UTF8')
|
||||
except Exception:
|
||||
pass
|
||||
import bumblebee_status.discover
|
||||
|
||||
bumblebee_status.discover.discover()
|
||||
|
||||
import core.config
|
||||
import core.output
|
||||
import core.module
|
||||
import core.input
|
||||
import core.event
|
||||
|
||||
import util.format
|
||||
|
||||
started = False
|
||||
|
||||
class CommandSocket(object):
|
||||
def __init__(self):
|
||||
self.__name = "/tmp/.bumblebee-status.{}".format(os.getpid())
|
||||
self.__socket = None
|
||||
|
||||
def __enter__(self):
|
||||
self.__socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.__socket.bind(self.__name)
|
||||
self.__socket.listen(5)
|
||||
return self.__socket
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.__socket.close()
|
||||
os.unlink(self.__name)
|
||||
|
||||
def process_event(event_line, config, update_lock):
|
||||
modules = {}
|
||||
try:
|
||||
event = json.loads(event_line)
|
||||
core.input.trigger(event)
|
||||
if "name" in event:
|
||||
modules[event["name"]] = True
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
delay = float(config.get("engine.input_delay", 0.0))
|
||||
if delay > 0:
|
||||
time.sleep(delay)
|
||||
if update_lock.acquire(blocking=False) == True:
|
||||
core.event.trigger("update", modules.keys(), force=True)
|
||||
core.event.trigger("draw")
|
||||
update_lock.release()
|
||||
|
||||
def handle_commands(config, update_lock):
|
||||
with CommandSocket() as cmdsocket:
|
||||
while True:
|
||||
tmp, _ = cmdsocket.accept()
|
||||
line = tmp.recv(4096).decode()
|
||||
tmp.close()
|
||||
logging.debug("socket event {}".format(line))
|
||||
process_event(line, config, update_lock)
|
||||
|
||||
|
||||
def handle_events(config, update_lock):
|
||||
while True:
|
||||
try:
|
||||
line = sys.stdin.readline().strip(",").strip()
|
||||
if line == "[": continue
|
||||
logging.info("input event: {}".format(line))
|
||||
process_event(line, config, update_lock)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
|
||||
def main():
|
||||
global started
|
||||
|
||||
config = core.config.Config(sys.argv[1:])
|
||||
level = logging.DEBUG if config.debug() else logging.ERROR
|
||||
if config.logfile():
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="[%(asctime)s] %(module)-16s %(levelname)-8s %(message)s",
|
||||
filename=os.path.abspath(os.path.expanduser(config.logfile())),
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="[%(asctime)s] %(module)-16s %(levelname)-8s %(message)s",
|
||||
stream=sys.stderr,
|
||||
)
|
||||
|
||||
theme = core.theme.Theme(config.theme(), config.iconset())
|
||||
output = core.output.i3(theme, config)
|
||||
modules = []
|
||||
|
||||
core.input.register(None, core.input.WHEEL_UP, "i3-msg workspace prev_on_output")
|
||||
core.input.register(None, core.input.WHEEL_DOWN, "i3-msg workspace next_on_output")
|
||||
|
||||
core.event.trigger("start")
|
||||
started = True
|
||||
|
||||
update_lock = threading.Lock()
|
||||
event_thread = threading.Thread(target=handle_events, args=(config, update_lock, ))
|
||||
event_thread.daemon = True
|
||||
event_thread.start()
|
||||
|
||||
cmd_thread = threading.Thread(target=handle_commands, args=(config, update_lock, ))
|
||||
cmd_thread.daemon = True
|
||||
cmd_thread.start()
|
||||
|
||||
def sig_USR1_handler(signum,stack):
|
||||
engine.write_output()
|
||||
|
||||
config = bumblebee.config.Config(sys.argv[1:])
|
||||
if update_lock.acquire(blocking=False) == True:
|
||||
core.event.trigger("update", force=True)
|
||||
core.event.trigger("draw")
|
||||
update_lock.release()
|
||||
|
||||
if config.debug():
|
||||
if config.logfile() in ["stdout", "stderr"]:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="[%(asctime)s] %(levelname)-8s %(message)s",
|
||||
stream=sys.stdout if config.logfile() == "stdout" else sys.stderr
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="[%(asctime)s] %(levelname)-8s %(message)s",
|
||||
filename=config.logfile()
|
||||
)
|
||||
modules.append(core.module.load("debug", config, theme))
|
||||
|
||||
theme = bumblebee.theme.Theme(config.theme(), config.iconset())
|
||||
output = bumblebee.output.I3BarOutput(theme=theme, config=config)
|
||||
inp = bumblebee.input.I3BarInput()
|
||||
engine = None
|
||||
for module in config.modules():
|
||||
modules.append(core.module.load(module, config, theme))
|
||||
modules[-1].register_callbacks()
|
||||
|
||||
if config.reverse():
|
||||
modules.reverse()
|
||||
|
||||
output.modules(modules)
|
||||
|
||||
if util.format.asbool(config.get("engine.collapsible", True)) == True:
|
||||
core.input.register(None, core.input.MIDDLE_MOUSE, output.toggle_minimize)
|
||||
|
||||
signal.signal(10, sig_USR1_handler)
|
||||
while True:
|
||||
if update_lock.acquire(blocking=False) == True:
|
||||
core.event.trigger("update")
|
||||
core.event.trigger("draw")
|
||||
update_lock.release()
|
||||
output.wait(config.interval())
|
||||
core.event.trigger("stop")
|
||||
|
||||
try:
|
||||
engine = bumblebee.engine.Engine(
|
||||
config=config,
|
||||
output=output,
|
||||
inp=inp,
|
||||
theme=theme,
|
||||
)
|
||||
signal.signal(10,sig_USR1_handler)
|
||||
engine.run()
|
||||
except KeyboardInterrupt as error:
|
||||
inp.stop()
|
||||
sys.exit(0)
|
||||
except BaseException as e:
|
||||
if not engine: raise
|
||||
module = engine.current_module()
|
||||
logging.exception(e)
|
||||
if output.started():
|
||||
output.flush()
|
||||
output.end()
|
||||
else:
|
||||
output.start()
|
||||
import time
|
||||
while True:
|
||||
output.begin()
|
||||
error = bumblebee.modules.error.Module(engine, {
|
||||
"config": config, "name": "error"
|
||||
})
|
||||
error.set("exception occurred: {} in {}".format(e, module))
|
||||
widget = error.widgets()[0]
|
||||
widget.link_module(error)
|
||||
output.draw(widget, error)
|
||||
output.flush()
|
||||
output.end()
|
||||
time.sleep(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
try:
|
||||
if sys.version_info.major < 3:
|
||||
raise Exception("at least Python 3.4 required (Python 2.x not supported)")
|
||||
main()
|
||||
except Exception as e:
|
||||
# really basic errors -> make sure these are shown in the status bar by minimal config
|
||||
logging.exception(e)
|
||||
if not started:
|
||||
print("{\"version\":1}")
|
||||
print("[")
|
||||
while True:
|
||||
sys.stdout.write(
|
||||
json.dumps(
|
||||
[
|
||||
{
|
||||
"full_text": " {} ".format(e),
|
||||
"background": "#ff0000",
|
||||
"color": "#ffffff",
|
||||
"name": "error",
|
||||
"instance": "the-only-one",
|
||||
}
|
||||
]
|
||||
)
|
||||
)
|
||||
sys.stdout.write(",\n")
|
||||
sys.stdout.flush()
|
||||
time.sleep(5)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
from ._version import get_versions
|
||||
__version__ = get_versions()['version']
|
||||
del get_versions
|
|
@ -1,140 +0,0 @@
|
|||
"""Configuration handling
|
||||
|
||||
This module provides configuration information (loaded modules,
|
||||
module parameters, etc.) to all other components
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import argparse
|
||||
import textwrap
|
||||
import importlib
|
||||
import bumblebee.store
|
||||
|
||||
MODULE_HELP = "Specify a space-separated list of modules to load. The order of the list determines their order in the i3bar (from left to right). Use <module>:<alias> to provide an alias in case you want to load the same module multiple times, but specify different parameters."
|
||||
THEME_HELP = "Specify the theme to use for drawing modules"
|
||||
PARAMETER_HELP = "Provide configuration parameters in the form of <module>.<key>=<value>"
|
||||
LIST_HELP = "Display a list of either available themes or available modules along with their parameters."
|
||||
DEBUG_HELP = "Enable debug log, This will create '~/bumblebee-status-debug.log' by default, can be changed with the '-f' option"
|
||||
ICONMARKUP_HELP = "A Python format string that is valid Pango markup used for low level customization of icons on top of themes. There is no validation performed, this is delegated to the user. Used together with --markup=pango. Example: \"<span foreground='#ffffff' background='#000000'>{}</span>\". WARNING: highly experimental feature"
|
||||
|
||||
class print_usage(argparse.Action):
|
||||
def __init__(self, option_strings, dest, nargs=None, **kwargs):
|
||||
argparse.Action.__init__(self, option_strings, dest, nargs, **kwargs)
|
||||
self._indent = " "*2
|
||||
|
||||
def __call__(self, parser, namespace, value, option_string=None):
|
||||
if value == "modules":
|
||||
self._args = namespace
|
||||
self.print_modules()
|
||||
elif value == "themes":
|
||||
self.print_themes()
|
||||
sys.exit(0)
|
||||
|
||||
def print_themes(self):
|
||||
print(", ".join(bumblebee.theme.themes()))
|
||||
|
||||
def print_modules(self):
|
||||
if self._args.list_format == "markdown":
|
||||
print("# Table of modules")
|
||||
print("|Name |Description |")
|
||||
print("|-----|------------|")
|
||||
|
||||
for m in bumblebee.engine.all_modules():
|
||||
try:
|
||||
mod = importlib.import_module("bumblebee.modules.{}".format(m["name"]))
|
||||
if self._args.list_format == "markdown":
|
||||
doc = mod.__doc__.replace("<", "\<")
|
||||
doc = doc.replace(">", "\>")
|
||||
doc = doc.replace("\n", "<br>")
|
||||
print("|{} |{} |".format(m["name"], doc))
|
||||
else:
|
||||
print(textwrap.fill("{}:".format(m["name"]), 80,
|
||||
initial_indent=self._indent*2, subsequent_indent=self._indent*2))
|
||||
for line in mod.__doc__.split("\n"):
|
||||
print(textwrap.fill(line, 80,
|
||||
initial_indent=self._indent*3, subsequent_indent=self._indent*6))
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_parser():
|
||||
"""Create the argument parser"""
|
||||
parser = argparse.ArgumentParser(description="display system data in the i3bar")
|
||||
parser.add_argument("-m", "--modules", nargs="+", action='append', default=[],
|
||||
help=MODULE_HELP)
|
||||
parser.add_argument("-t", "--theme", default="default", help=THEME_HELP)
|
||||
parser.add_argument("--markup", default="none", help="Specify the markup type of the output (e.g. 'pango')")
|
||||
parser.add_argument("--iconmarkup", default="none", help=ICONMARKUP_HELP)
|
||||
parser.add_argument("-p", "--parameters", nargs="+", action='append', default=[],
|
||||
help=PARAMETER_HELP)
|
||||
parser.add_argument("-l", "--list", choices=["modules", "themes"], action=print_usage,
|
||||
help=LIST_HELP)
|
||||
parser.add_argument("--list-format", choices=["plain", "markdown"], help="output format of -l, *must* be specified before -l")
|
||||
parser.add_argument("-d", "--debug", action="store_true",
|
||||
help=DEBUG_HELP)
|
||||
parser.add_argument("-r", "--right-to-left", action="store_true",
|
||||
help="Draw widgets from right to left, rather than left to right (which is the default)")
|
||||
parser.add_argument("-f", "--logfile", default="~/bumblebee-status-debug.log",
|
||||
help="Location of the debug log file")
|
||||
parser.add_argument("-i", "--iconset", default="auto",
|
||||
help="Specify the name of an iconset to use (overrides theme default)")
|
||||
parser.add_argument("-a", "--autohide", nargs="+", default=[],
|
||||
help="Specify a list of modules to hide when not in warning/error state")
|
||||
|
||||
return parser
|
||||
|
||||
class Config(bumblebee.store.Store):
|
||||
"""Top-level configuration class
|
||||
|
||||
Parses commandline arguments and provides non-module
|
||||
specific configuration information.
|
||||
"""
|
||||
def __init__(self, args=None):
|
||||
super(Config, self).__init__()
|
||||
parser = create_parser()
|
||||
self._args = parser.parse_args(args if args else [])
|
||||
|
||||
if not self._args.debug:
|
||||
logging.getLogger().disabled = True
|
||||
|
||||
parameters = [item for sub in self._args.parameters for item in sub]
|
||||
for param in parameters:
|
||||
key, value = param.split("=", 1)
|
||||
self.set(key, value)
|
||||
|
||||
def modules(self):
|
||||
modules = [item for sub in self._args.modules for item in sub]
|
||||
"""Return a list of all activated modules"""
|
||||
return [{
|
||||
"module": x.split(":")[0],
|
||||
"name": x if not ":" in x else x.split(":")[1],
|
||||
} for x in modules]
|
||||
|
||||
def theme(self):
|
||||
"""Return the name of the selected theme"""
|
||||
return self._args.theme
|
||||
|
||||
def iconset(self):
|
||||
"""Return the name of a user-specified icon-set"""
|
||||
return self._args.iconset
|
||||
|
||||
def debug(self):
|
||||
return self._args.debug
|
||||
|
||||
def reverse(self):
|
||||
return self._args.right_to_left
|
||||
|
||||
def logfile(self):
|
||||
return os.path.expanduser(self._args.logfile)
|
||||
|
||||
def autohide(self):
|
||||
return self._args.autohide
|
||||
|
||||
def markup(self):
|
||||
return self._args.markup
|
||||
|
||||
def iconmarkup(self):
|
||||
return self._args.iconmarkup
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,313 +0,0 @@
|
|||
"""Core application engine"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import pkgutil
|
||||
import logging
|
||||
import importlib
|
||||
import bumblebee.error
|
||||
import bumblebee.modules
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from ConfigParser import RawConfigParser
|
||||
except ImportError:
|
||||
from configparser import RawConfigParser
|
||||
|
||||
def all_modules():
|
||||
"""Return a list of available modules"""
|
||||
result = []
|
||||
path = os.path.dirname(bumblebee.modules.__file__)
|
||||
for mod in [name for _, name, _ in pkgutil.iter_modules([path])]:
|
||||
result.append({
|
||||
"name": mod
|
||||
})
|
||||
return result
|
||||
|
||||
class Module(object):
|
||||
"""Module instance base class
|
||||
|
||||
Objects of this type represent the modules that
|
||||
the user configures. Concrete module implementations
|
||||
(e.g. CPU utilization, disk usage, etc.) derive from
|
||||
this base class.
|
||||
"""
|
||||
def __init__(self, engine, config={}, widgets=[]):
|
||||
self.name = config.get("name", self.__module__.split(".")[-1])
|
||||
self._config = config
|
||||
self.id = self.name
|
||||
self.error = None
|
||||
self._next = int(time.time())
|
||||
self._default_interval = 0
|
||||
self._interval_factor = 1
|
||||
self._engine = engine
|
||||
|
||||
self._configFile = None
|
||||
for cfg in [os.path.expanduser("~/.bumblebee-status.conf"), os.path.expanduser("~/.config/bumblebee-status.conf")]:
|
||||
if os.path.exists(cfg):
|
||||
self._configFile = RawConfigParser()
|
||||
self._configFile.read(cfg)
|
||||
log.debug("reading configuration file {}".format(cfg))
|
||||
break
|
||||
|
||||
if self._configFile is not None and self._configFile.has_section("module-parameters"):
|
||||
log.debug(self._configFile.items("module-parameters"))
|
||||
self._widgets = []
|
||||
if widgets:
|
||||
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
||||
|
||||
def theme(self):
|
||||
return self._engine.theme()
|
||||
|
||||
def widgets(self, widgets=None):
|
||||
"""Return the widgets to draw for this module"""
|
||||
if widgets:
|
||||
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
||||
return self._widgets
|
||||
|
||||
def hidden(self):
|
||||
return False
|
||||
|
||||
def widget(self, name):
|
||||
for widget in self._widgets:
|
||||
if widget.name == name:
|
||||
return widget
|
||||
|
||||
def errorWidget(self):
|
||||
msg = self.error
|
||||
if len(msg) > 10:
|
||||
msg = "{}...".format(msg[0:7])
|
||||
return bumblebee.output.Widget(full_text="error: {}".format(msg))
|
||||
|
||||
def widget_by_id(self, uid):
|
||||
for widget in self._widgets:
|
||||
if widget.id == uid:
|
||||
return widget
|
||||
return None
|
||||
|
||||
def update(self, widgets):
|
||||
"""By default, update() is a NOP"""
|
||||
pass
|
||||
|
||||
def update_wrapper(self, widgets):
|
||||
if self._next > int(time.time()):
|
||||
return
|
||||
try:
|
||||
self.error = None
|
||||
self.update(self._widgets)
|
||||
except Exception as e:
|
||||
log.error("error updating '{}': {}".format(self.name, str(e)))
|
||||
self.error = str(e)
|
||||
self._next += int(self.parameter("interval", self._default_interval))*self._interval_factor
|
||||
|
||||
def interval_factor(self, factor):
|
||||
self._interval_factor = factor
|
||||
|
||||
def interval(self, intvl):
|
||||
self._default_interval = intvl
|
||||
self._next = int(time.time())
|
||||
|
||||
def update_all(self):
|
||||
self.update_wrapper(self._widgets)
|
||||
|
||||
def has_parameter(self, name):
|
||||
v = self.parameter(name)
|
||||
return v is not None
|
||||
|
||||
def parameter(self, name, default=None):
|
||||
"""Return the config parameter 'name' for this module"""
|
||||
module_name = "{}.{}".format(self.__module__.split(".")[-1], name)
|
||||
name = "{}.{}".format(self.name, name)
|
||||
value = self._config["config"].get(module_name, default)
|
||||
value = self._config["config"].get(name, value)
|
||||
if value == default:
|
||||
try:
|
||||
value = self._configFile.get("module-parameters", name)
|
||||
except:
|
||||
pass
|
||||
return value
|
||||
|
||||
def threshold_state(self, value, warn, crit):
|
||||
if value > float(self.parameter("critical", crit)):
|
||||
return "critical"
|
||||
if value > float(self.parameter("warning", warn)):
|
||||
return "warning"
|
||||
return None
|
||||
|
||||
class Engine(object):
|
||||
"""Engine for driving the application
|
||||
|
||||
This class connects input/output, instantiates all
|
||||
required modules and drives the "event loop"
|
||||
"""
|
||||
def __init__(self, config, output=None, inp=None, theme=None):
|
||||
self._output = output
|
||||
self._config = config
|
||||
self._running = True
|
||||
self._modules = []
|
||||
self.input = inp
|
||||
self._aliases = self._aliases()
|
||||
self.load_modules(config.modules())
|
||||
self._current_module = None
|
||||
self._theme = theme
|
||||
|
||||
if bumblebee.util.asbool(config.get("engine.workspacewheel", "true")):
|
||||
if bumblebee.util.asbool(config.get("engine.workspacewrap", "true")):
|
||||
self.input.register_callback(None, bumblebee.input.WHEEL_UP,
|
||||
"i3-msg workspace prev_on_output")
|
||||
self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
|
||||
"i3-msg workspace next_on_output")
|
||||
else:
|
||||
self.input.register_callback(None, bumblebee.input.WHEEL_UP,
|
||||
cmd=self._prev_workspace)
|
||||
self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
|
||||
cmd=self._next_workspace)
|
||||
if bumblebee.util.asbool(config.get("engine.collapsible", "true")):
|
||||
self.input.register_callback(None, bumblebee.input.MIDDLE_MOUSE,
|
||||
cmd=self._toggle_minimize)
|
||||
|
||||
self.input.start()
|
||||
|
||||
def theme(self):
|
||||
return self._theme
|
||||
|
||||
def _toggle_minimize(self, event):
|
||||
for module in self._modules:
|
||||
widget = module.widget_by_id(event["instance"])
|
||||
if widget:
|
||||
log.debug("module {} found - toggle minimize".format(module.id))
|
||||
widget.toggle_minimize()
|
||||
|
||||
def _prev_workspace(self, event):
|
||||
self._change_workspace(-1)
|
||||
|
||||
def _next_workspace(self, event):
|
||||
self._change_workspace(1)
|
||||
|
||||
def _change_workspace(self, amount):
|
||||
try:
|
||||
active_output = None
|
||||
active_index = -1
|
||||
output_workspaces = {}
|
||||
data = json.loads(bumblebee.util.execute("i3-msg -t get_workspaces"))
|
||||
for workspace in data:
|
||||
output_workspaces.setdefault(workspace["output"], []).append(workspace)
|
||||
if workspace["focused"]:
|
||||
active_output = workspace["output"]
|
||||
active_index = len(output_workspaces[workspace["output"]]) - 1
|
||||
if (active_index + amount) < 0:
|
||||
return
|
||||
if (active_index + amount) >= len(output_workspaces[active_output]):
|
||||
return
|
||||
|
||||
while amount != 0:
|
||||
if amount > 0:
|
||||
bumblebee.util.execute("i3-msg workspace next_on_output")
|
||||
amount = amount - 1
|
||||
if amount < 0:
|
||||
bumblebee.util.execute("i3-msg workspace prev_on_output")
|
||||
amount = amount + 1
|
||||
except Exception as e:
|
||||
log.error("failed to change workspace: {}".format(e))
|
||||
|
||||
def modules(self):
|
||||
return self._modules
|
||||
|
||||
def load_modules(self, modules):
|
||||
"""Load specified modules and return them as list"""
|
||||
for module in modules:
|
||||
mod = self._load_module(module["module"], module["name"])
|
||||
self._modules.append(mod)
|
||||
self._register_module_callbacks(mod)
|
||||
return self._modules
|
||||
|
||||
def _register_module_callbacks(self, module):
|
||||
buttons = [
|
||||
{"name": "left-click", "id": bumblebee.input.LEFT_MOUSE},
|
||||
{"name": "middle-click", "id": bumblebee.input.MIDDLE_MOUSE},
|
||||
{"name": "right-click", "id": bumblebee.input.RIGHT_MOUSE},
|
||||
{"name": "wheel-up", "id": bumblebee.input.WHEEL_UP},
|
||||
{"name": "wheel-down", "id": bumblebee.input.WHEEL_DOWN},
|
||||
]
|
||||
for button in buttons:
|
||||
if module.parameter(button["name"], None):
|
||||
self.input.register_callback(obj=module,
|
||||
button=button["id"], cmd=module.parameter(button["name"]))
|
||||
|
||||
def _aliases(self):
|
||||
return {
|
||||
'date': 'datetime',
|
||||
'time': 'datetime',
|
||||
'datetz': 'datetimetz',
|
||||
'timetz': 'datetimetz',
|
||||
'pasink': 'pulseaudio',
|
||||
'pasource': 'pulseaudio',
|
||||
'test-alias': 'test',
|
||||
}
|
||||
|
||||
def _load_module(self, module_name, config_name=None):
|
||||
"""Load specified module and return it as object"""
|
||||
if module_name in self._aliases:
|
||||
config_name is config_name if config_name else module_name
|
||||
module_name = self._aliases[module_name]
|
||||
if config_name is None:
|
||||
config_name = module_name
|
||||
err = None
|
||||
try:
|
||||
module = importlib.import_module("bumblebee.modules.{}".format(module_name))
|
||||
except ImportError as error:
|
||||
err = error
|
||||
log.fatal("failed to import {}: {}".format(module_name, str(error)))
|
||||
if err:
|
||||
raise bumblebee.error.ModuleLoadError("unable to load module {}: {}".format(module_name, str(err)))
|
||||
return getattr(module, "Module")(self, {
|
||||
"name": config_name,
|
||||
"config": self._config
|
||||
})
|
||||
|
||||
def running(self):
|
||||
"""Check whether the event loop is running"""
|
||||
return self._running
|
||||
|
||||
def stop(self):
|
||||
"""Stop the event loop"""
|
||||
self._running = False
|
||||
|
||||
def current_module(self):
|
||||
return self._current_module.__module__
|
||||
|
||||
def run(self):
|
||||
"""Start the event loop"""
|
||||
self._output.start()
|
||||
while self.running():
|
||||
self.write_output()
|
||||
if self.running():
|
||||
self.input.wait(float(self._config.get("interval", 1)))
|
||||
|
||||
self._output.stop()
|
||||
self.input.stop()
|
||||
|
||||
def write_output(self):
|
||||
self._output.begin()
|
||||
for module in self._modules:
|
||||
self._current_module = module
|
||||
module.update_wrapper(module.widgets())
|
||||
if module.error is None:
|
||||
widget_ids = []
|
||||
if module.parameter('id'):
|
||||
widget_ids = module.parameter('id').split(',')
|
||||
idx = 0
|
||||
for widget in module.widgets():
|
||||
widget.link_module(module)
|
||||
if idx < len(widget_ids):
|
||||
widget.id = widget_ids[idx]
|
||||
idx = idx + 1
|
||||
self._output.draw(widget=widget, module=module, engine=self)
|
||||
else:
|
||||
self._output.draw(widget=module.errorWidget(), module=module, engine=self)
|
||||
self._output.flush()
|
||||
self._output.end()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,15 +0,0 @@
|
|||
"""Collection of all exceptions raised by this tool"""
|
||||
|
||||
class BaseError(Exception):
|
||||
"""Base class for all exceptions generated by this tool"""
|
||||
pass
|
||||
|
||||
class ModuleLoadError(BaseError):
|
||||
"""Raised whenever loading a module fails"""
|
||||
pass
|
||||
|
||||
class ThemeLoadError(BaseError):
|
||||
"""Raised whenever loading a theme fails"""
|
||||
pass
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,175 +0,0 @@
|
|||
"""Input classes"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
import time
|
||||
import socket
|
||||
import select
|
||||
import logging
|
||||
import threading
|
||||
import bumblebee.util
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
LEFT_MOUSE = 1
|
||||
MIDDLE_MOUSE = 2
|
||||
RIGHT_MOUSE = 3
|
||||
WHEEL_UP = 4
|
||||
WHEEL_DOWN = 5
|
||||
|
||||
def is_terminated():
|
||||
for thread in threading.enumerate():
|
||||
if thread.name == "MainThread" and not thread.is_alive():
|
||||
return True
|
||||
return False
|
||||
|
||||
class CommandSocket(object):
|
||||
def __init__(self):
|
||||
self._name = "/tmp/.bumblebee-status.{}".format(os.getpid())
|
||||
self._socket = None
|
||||
|
||||
def __enter__(self):
|
||||
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self._socket.bind(self._name)
|
||||
self._socket.listen(5)
|
||||
return self._socket
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self._socket.close()
|
||||
os.unlink(self._name)
|
||||
|
||||
def read_input(inp):
|
||||
"""Read i3bar input and execute callbacks"""
|
||||
|
||||
with CommandSocket() as cmdsocket:
|
||||
poll = select.poll()
|
||||
poll.register(sys.stdin, select.POLLIN)
|
||||
poll.register(cmdsocket, select.POLLIN)
|
||||
log.debug("starting click event processing")
|
||||
while inp.running:
|
||||
if is_terminated():
|
||||
return
|
||||
|
||||
try:
|
||||
events = poll.poll(1000)
|
||||
except Exception:
|
||||
continue
|
||||
for fileno, event in events:
|
||||
|
||||
if fileno == cmdsocket.fileno():
|
||||
tmp, _ = cmdsocket.accept()
|
||||
line = tmp.recv(4096).decode()
|
||||
tmp.close()
|
||||
else:
|
||||
line = "["
|
||||
while line.startswith("["):
|
||||
line = sys.stdin.readline().strip(",").strip()
|
||||
log.debug("new event: {}".format(line))
|
||||
inp.has_event = True
|
||||
try:
|
||||
event = json.loads(line)
|
||||
if "instance" in event:
|
||||
inp.callback(event)
|
||||
inp.redraw()
|
||||
else:
|
||||
log.debug("field 'instance' missing in input, not processing the event")
|
||||
except ValueError as e:
|
||||
log.debug("failed to parse event: {}".format(e))
|
||||
log.debug("exiting click event processing")
|
||||
poll.unregister(sys.stdin.fileno())
|
||||
inp.has_event = True
|
||||
inp.clean_exit = True
|
||||
|
||||
class I3BarInput(object):
|
||||
"""Process incoming events from the i3bar"""
|
||||
def __init__(self):
|
||||
self.running = True
|
||||
self._callbacks = {}
|
||||
self.clean_exit = False
|
||||
self.global_id = str(uuid.uuid4())
|
||||
self.need_event = False
|
||||
self.has_event = False
|
||||
self._condition = threading.Condition()
|
||||
|
||||
def start(self):
|
||||
"""Start asynchronous input processing"""
|
||||
self.has_event = False
|
||||
self.running = True
|
||||
self._condition.acquire()
|
||||
self._thread = threading.Thread(target=read_input, args=(self,))
|
||||
self._thread.start()
|
||||
|
||||
def redraw(self):
|
||||
self._condition.acquire()
|
||||
self._condition.notify()
|
||||
self._condition.release()
|
||||
|
||||
def alive(self):
|
||||
"""Check whether the input processing is still active"""
|
||||
return self._thread.is_alive()
|
||||
|
||||
def wait(self, timeout):
|
||||
self._condition.wait(timeout)
|
||||
|
||||
def _wait(self):
|
||||
while not self.has_event:
|
||||
time.sleep(0.1)
|
||||
self.has_event = False
|
||||
|
||||
def stop(self):
|
||||
"""Stop asynchronous input processing"""
|
||||
self._condition.release()
|
||||
if self.need_event:
|
||||
self._wait()
|
||||
self.running = False
|
||||
self._thread.join()
|
||||
return self.clean_exit
|
||||
|
||||
def _uuidstr(self, name, button):
|
||||
return "{}::{}".format(name, button)
|
||||
|
||||
def _uid(self, obj, button):
|
||||
uid = self.global_id
|
||||
if obj:
|
||||
uid = obj.id
|
||||
return self._uuidstr(uid, button)
|
||||
|
||||
def deregister_callbacks(self, obj):
|
||||
to_delete = []
|
||||
uid = obj.id if obj else self.global_id
|
||||
for key in self._callbacks:
|
||||
if uid in key:
|
||||
to_delete.append(key)
|
||||
for key in to_delete:
|
||||
del self._callbacks[key]
|
||||
|
||||
def register_callback(self, obj, button, cmd):
|
||||
"""Register a callback function or system call"""
|
||||
uid = self._uid(obj, button)
|
||||
if uid not in self._callbacks:
|
||||
self._callbacks[uid] = {}
|
||||
self._callbacks[uid] = cmd
|
||||
|
||||
def callback(self, event):
|
||||
"""Execute callback action for an incoming event"""
|
||||
button = event["button"]
|
||||
|
||||
cmd = self._callbacks.get(self._uuidstr(self.global_id, button), None)
|
||||
cmd = self._callbacks.get(self._uuidstr(event["name"], button), cmd)
|
||||
cmd = self._callbacks.get(self._uuidstr(event["instance"], button), cmd)
|
||||
|
||||
if cmd is None:
|
||||
return
|
||||
try:
|
||||
if callable(cmd):
|
||||
cmd(event)
|
||||
else:
|
||||
bumblebee.util.execute(cmd, False)
|
||||
except Exception:
|
||||
# fall back to global default
|
||||
if not "__fallback" in event:
|
||||
return self.callback({"name": None, "instance": None, "__fallback": True, "button": event["button"]})
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,42 +0,0 @@
|
|||
"""get volume level
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.volume)
|
||||
)
|
||||
self._level = "0"
|
||||
self._muted = True
|
||||
device = self.parameter("device", "Master,0")
|
||||
self._cmdString = "amixer get {}".format(device)
|
||||
|
||||
def volume(self, widget):
|
||||
m = re.search(r'([\d]+)\%', self._level)
|
||||
self._muted = True
|
||||
if m:
|
||||
if m.group(1) != "0" and "[on]" in self._level:
|
||||
self._muted = False
|
||||
return "{}%".format(m.group(1))
|
||||
else:
|
||||
return "0%"
|
||||
|
||||
def update(self, widgets):
|
||||
level = ""
|
||||
try:
|
||||
level = bumblebee.util.execute(self._cmdString)
|
||||
except Exception as e:
|
||||
level = ""
|
||||
|
||||
self._level = level
|
||||
|
||||
def state(self, widget):
|
||||
if self._muted:
|
||||
return ["warning", "muted"]
|
||||
return ["unmuted"]
|
|
@ -1,81 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays APT package update information (<to upgrade>/<to remove >)
|
||||
Requires the following debian packages:
|
||||
* python-parse
|
||||
* aptitude
|
||||
|
||||
"""
|
||||
|
||||
import threading
|
||||
from parse import *
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
APT_CHECK_PATH = ("aptitude full-upgrade --simulate --assume-yes")
|
||||
PATTERN = "{} packages upgraded, {} newly installed, {} to remove and {} not upgraded."
|
||||
|
||||
def parse_result(to_parse):
|
||||
# We want to line with the iforamtion about package upgrade
|
||||
line_to_parse = to_parse.split("\n")[-4]
|
||||
|
||||
result = parse(PATTERN, line_to_parse)
|
||||
|
||||
return int(result[0]), int(result[2])
|
||||
|
||||
def get_apt_check_info(widget):
|
||||
try:
|
||||
res = bumblebee.util.execute(APT_CHECK_PATH)
|
||||
widget.set("error", None)
|
||||
except (RuntimeError, FileNotFoundError) as e:
|
||||
widget.set("error", "unable to query APT: {}".format(e))
|
||||
return
|
||||
|
||||
to_upgrade = 0
|
||||
to_remove = 0
|
||||
try:
|
||||
to_upgrade, to_remove = parse_result(res)
|
||||
except e:
|
||||
widget.set("error", "parse error: {}".format(e))
|
||||
return
|
||||
|
||||
widget.set("to_upgrade", to_upgrade)
|
||||
widget.set("to_remove", to_remove)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.updates)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
self.interval_factor(60)
|
||||
self.interval(30)
|
||||
|
||||
def updates(self, widget):
|
||||
result = []
|
||||
if widget.get("error"):
|
||||
return widget.get("error")
|
||||
for t in ["to_upgrade", "to_remove"]:
|
||||
result.append(str(widget.get(t, 0)))
|
||||
return "/".join(result)
|
||||
|
||||
def update(self, widgets):
|
||||
thread = threading.Thread(target=get_apt_check_info, args=(widgets[0],))
|
||||
thread.start()
|
||||
|
||||
def state(self, widget):
|
||||
cnt = 0
|
||||
ret = "good"
|
||||
for t in ["to_upgrade", "to_remove"]:
|
||||
cnt += widget.get(t, 0)
|
||||
if cnt > 50:
|
||||
ret = "critical"
|
||||
elif cnt > 0:
|
||||
ret = "warning"
|
||||
if widget.get("error"):
|
||||
ret = "critical"
|
||||
|
||||
return ret
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,51 +0,0 @@
|
|||
"""Check updates to Arch Linux.
|
||||
|
||||
Requires the following executable:
|
||||
* checkupdates (from pacman-contrib)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import subprocess
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.utilization)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
self.packages = self.check_updates()
|
||||
|
||||
def check_updates(self):
|
||||
p = subprocess.Popen(
|
||||
"checkupdates", stdout=subprocess.PIPE, shell=True)
|
||||
|
||||
p_status = p.wait()
|
||||
|
||||
if p_status == 0:
|
||||
(output, err) = p.communicate()
|
||||
|
||||
output = output.decode('utf-8')
|
||||
packages = output.split('\n')
|
||||
packages.pop()
|
||||
|
||||
return len(packages)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def _format(self):
|
||||
return self.parameter("format", "Update Arch: {}")
|
||||
|
||||
def utilization(self, widget):
|
||||
return self._format.format(self.packages)
|
||||
|
||||
def hidden(self):
|
||||
return self.check_updates() == 0
|
||||
|
||||
def update(self, widgets):
|
||||
self.packages = self.check_updates()
|
||||
|
||||
def state(self, widget):
|
||||
return self.threshold_state(self.packages, 1, 100)
|
|
@ -1,140 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays battery status, remaining percentage and charging information.
|
||||
|
||||
Parameters:
|
||||
* battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)
|
||||
* battery.warning : Warning threshold in % of remaining charge (defaults to 20)
|
||||
* battery.critical : Critical threshold in % of remaining charge (defaults to 10)
|
||||
* battery.showdevice : If set to "true", add the device name to the widget (defaults to False)
|
||||
* battery.decorate : If set to "false", hides additional icons (charging, etc.) (defaults to True)
|
||||
* battery.showpowerconsumption: If set to "true", show current power consumption (defaults to False)
|
||||
"""
|
||||
|
||||
import os
|
||||
import glob
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
try:
|
||||
import power
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = []
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
self._batteries = self.parameter("device", "auto").split(",")
|
||||
if self._batteries[0] == "auto":
|
||||
self._batteries = glob.glob("/sys/class/power_supply/BAT*")
|
||||
else:
|
||||
self._batteries = ["/sys/class/power_supply/{}".format(b) for b in self._batteries]
|
||||
if len(self._batteries) == 0:
|
||||
self._batteries = ["/sys/class/power_supply/BAT0"]
|
||||
self.update(widgets)
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="gnome-power-statistics")
|
||||
|
||||
def update(self, widgets):
|
||||
new_widgets = []
|
||||
for path in self._batteries:
|
||||
widget = self.widget(path)
|
||||
if not widget:
|
||||
widget = bumblebee.output.Widget(full_text=self.capacity, name=path)
|
||||
new_widgets.append(widget)
|
||||
self.capacity(widget)
|
||||
while len(widgets) > 0: del widgets[0]
|
||||
for widget in new_widgets:
|
||||
if bumblebee.util.asbool(self.parameter("decorate", True)) == False:
|
||||
widget.set("theme.exclude", "suffix")
|
||||
widgets.append(widget)
|
||||
self._widgets = widgets
|
||||
|
||||
def remaining(self):
|
||||
estimate = 0.0
|
||||
try:
|
||||
estimate = power.PowerManagement().get_time_remaining_estimate()
|
||||
# do not show remaining if on AC
|
||||
if estimate == power.common.TIME_REMAINING_UNLIMITED:
|
||||
return None
|
||||
if estimate == power.common.TIME_REMAINING_UNKNOWN:
|
||||
return ""
|
||||
except Exception:
|
||||
return ""
|
||||
return bumblebee.util.durationfmt(estimate*60, shorten=True, suffix=True) # estimate is in minutes
|
||||
|
||||
def capacity(self, widget):
|
||||
widget.set("capacity", -1)
|
||||
widget.set("ac", False)
|
||||
if not os.path.exists(widget.name):
|
||||
widget.set("capacity", 100)
|
||||
widget.set("ac", True)
|
||||
return "ac"
|
||||
capacity = 100
|
||||
try:
|
||||
with open("{}/capacity".format(widget.name)) as f:
|
||||
capacity = int(f.read())
|
||||
except IOError:
|
||||
return "n/a"
|
||||
|
||||
capacity = capacity if capacity < 100 else 100
|
||||
widget.set("capacity", capacity)
|
||||
|
||||
# Read power conumption
|
||||
if bumblebee.util.asbool(self.parameter("showpowerconsumption", False)):
|
||||
r=open(widget.name + '/power_now', "r")
|
||||
output = "{}% ({})".format(capacity,str(int(r.read())/1000000) + "W")
|
||||
else:
|
||||
output = "{}%".format(capacity)
|
||||
|
||||
widget.set("theme.minwidth", "100%")
|
||||
if bumblebee.util.asbool(self.parameter("showremaining", True))\
|
||||
and self.getCharge(widget) == "Discharging":
|
||||
output = "{} {}".format(output, self.remaining())
|
||||
|
||||
if bumblebee.util.asbool(self.parameter("showdevice", False)):
|
||||
output = "{} ({})".format(output, os.path.basename(widget.name))
|
||||
|
||||
return output
|
||||
|
||||
def state(self, widget):
|
||||
state = []
|
||||
capacity = widget.get("capacity")
|
||||
|
||||
if capacity < 0:
|
||||
return ["critical", "unknown"]
|
||||
|
||||
if capacity < int(self.parameter("critical", 10)):
|
||||
state.append("critical")
|
||||
elif capacity < int(self.parameter("warning", 20)):
|
||||
state.append("warning")
|
||||
|
||||
if widget.get("ac"):
|
||||
state.append("AC")
|
||||
else:
|
||||
charge = self.getCharge(widget)
|
||||
if charge == "Discharging":
|
||||
state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
||||
elif charge == "Unknown":
|
||||
state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
||||
else:
|
||||
if capacity > 95:
|
||||
state.append("charged")
|
||||
else:
|
||||
state.append("charging")
|
||||
|
||||
return state
|
||||
|
||||
def getCharge(self, widget):
|
||||
charge = ""
|
||||
try:
|
||||
with open("{}/status".format(widget.name)) as f:
|
||||
charge = f.read().strip()
|
||||
except IOError:
|
||||
pass
|
||||
return charge
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,130 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays battery status, remaining percentage and charging information.
|
||||
|
||||
Parameters:
|
||||
* battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)
|
||||
* battery.warning : Warning threshold in % of remaining charge (defaults to 20)
|
||||
* battery.critical : Critical threshold in % of remaining charge (defaults to 10)
|
||||
* batter.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged
|
||||
"""
|
||||
|
||||
import os
|
||||
import glob
|
||||
import logging
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
try:
|
||||
import power
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
self._batteries = []
|
||||
try:
|
||||
for battery in os.listdir('/sys/class/power_supply/'):
|
||||
if not any(i in battery for i in ['AC', 'hidpp']):
|
||||
self._batteries.append("/sys/class/power_supply/" + battery)
|
||||
except Exception as e:
|
||||
logging.exception("unable to detect batteries: {}".format(str(e)))
|
||||
|
||||
super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.capacity))
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="gnome-power-statistics")
|
||||
|
||||
def remaining(self):
|
||||
estimate = 0.0
|
||||
power_now = 0.0
|
||||
try:
|
||||
estimate = power.PowerManagement().get_time_remaining_estimate()
|
||||
# do not show remaining if on AC
|
||||
if estimate == power.common.TIME_REMAINING_UNLIMITED:
|
||||
return None
|
||||
elif estimate == power.common.TIME_REMAINING_UNKNOWN:
|
||||
return ""
|
||||
except Exception:
|
||||
return ""
|
||||
return bumblebee.util.durationfmt(estimate*60, shorten=True, suffix=True) # estimate is in minutes
|
||||
|
||||
def capacity(self, widget):
|
||||
widget.set("capacity", -1)
|
||||
widget.set("ac", False)
|
||||
capacity = 100
|
||||
self.energy_now = 0
|
||||
self.energy_full = 0
|
||||
errors = 0
|
||||
for path in self._batteries:
|
||||
try:
|
||||
with open("{}/energy_full".format(path)) as f:
|
||||
self.energy_full += int(f.read())
|
||||
with open("{}/energy_now".format(path)) as o:
|
||||
self.energy_now += int(o.read())
|
||||
except IOError:
|
||||
return "n/a"
|
||||
except Exception:
|
||||
errors += 1
|
||||
|
||||
if errors == len(self._batteries):
|
||||
return "n/a"
|
||||
|
||||
capacity = int( float(self.energy_now) / float(self.energy_full) * 100.0)
|
||||
capacity = capacity if capacity < 100 else 100
|
||||
widget.set("capacity", capacity)
|
||||
output = "{}%".format(capacity)
|
||||
widget.set("theme.minwidth", "100%")
|
||||
|
||||
if bumblebee.util.asbool(self.parameter("showremaining", True))\
|
||||
and self.getCharge(widget) == "Discharging":
|
||||
output = "{} {}".format(output, self.remaining())
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def state(self, widget):
|
||||
state = []
|
||||
capacity = widget.get("capacity", -1)
|
||||
|
||||
if capacity < 0:
|
||||
return ["critical", "unknown"]
|
||||
|
||||
if capacity < int(self.parameter("critical", 10)):
|
||||
state.append("critical")
|
||||
elif capacity < int(self.parameter("warning", 20)):
|
||||
state.append("warning")
|
||||
|
||||
if widget.get("ac"):
|
||||
state.append("AC")
|
||||
else:
|
||||
charge = self.getCharge(widget)
|
||||
if charge == "Discharging":
|
||||
state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
||||
elif charge == "Unknown":
|
||||
state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
||||
else:
|
||||
if capacity > 95:
|
||||
state.append("charged")
|
||||
else:
|
||||
state.append("charging")
|
||||
|
||||
return state
|
||||
|
||||
def getCharge(self, widget):
|
||||
charge = ""
|
||||
charge_list = []
|
||||
for x in range(len(self._batteries)):
|
||||
try:
|
||||
with open("{}/status".format(self._batteries[x])) as f:
|
||||
charge_list.append(f.read().strip())
|
||||
except IOError:
|
||||
pass
|
||||
for x in range(len(charge_list)):
|
||||
if charge_list[x] == "Discharging":
|
||||
charge = charge_list[x]
|
||||
break
|
||||
return charge
|
|
@ -1,104 +0,0 @@
|
|||
"""Displays bluetooth status. Left mouse click launches manager app,
|
||||
right click toggles bluetooth. Needs dbus-send to toggle bluetooth state and
|
||||
python-dbus to count the number of connections
|
||||
|
||||
Parameters:
|
||||
* bluetooth.manager : application to launch on click (blueman-manager)
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import dbus
|
||||
import dbus.mainloop.glib
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
import bumblebee.popup
|
||||
import logging
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
"""Bluetooth module."""
|
||||
|
||||
def __init__(self, engine, config):
|
||||
"""Initialize."""
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(
|
||||
full_text=self.status))
|
||||
|
||||
self.manager = self.parameter("manager", "blueman-manager")
|
||||
self._status = "Off"
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
self._bus = dbus.SystemBus()
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.manager)
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
||||
cmd=self._toggle)
|
||||
|
||||
def status(self, widget):
|
||||
"""Get status."""
|
||||
return self._status
|
||||
|
||||
def update(self, widgets):
|
||||
"""Update current state."""
|
||||
state = len(subprocess.run(['bluetoothctl', 'list'], stdout=subprocess.PIPE).stdout)
|
||||
if state > 0:
|
||||
connected_devices = self.get_connected_devices()
|
||||
self._status = "On - {}".format(connected_devices)
|
||||
else:
|
||||
self._status = "Off"
|
||||
adapters_cmd = 'rfkill list | grep Bluetooth'
|
||||
if not len(subprocess.run(adapters_cmd, shell=True, stdout=subprocess.PIPE).stdout):
|
||||
self._status = "No Adapter Found"
|
||||
return
|
||||
|
||||
def manager(self, widget):
|
||||
"""Launch manager."""
|
||||
bumblebee.util.execute(self.manager)
|
||||
|
||||
def _toggle(self, widget=None):
|
||||
"""Toggle bluetooth state."""
|
||||
if "On" in self._status:
|
||||
state = "false"
|
||||
else:
|
||||
state = "true"
|
||||
|
||||
cmd = "dbus-send --system --print-reply --dest=org.blueman.Mechanism /org/blueman/mechanism org.blueman.Mechanism.SetRfkillState boolean:%s" % state
|
||||
|
||||
logging.debug('bt: toggling bluetooth')
|
||||
bumblebee.util.execute(cmd)
|
||||
|
||||
def state(self, widget):
|
||||
"""Get current state."""
|
||||
state = []
|
||||
|
||||
if self._status == "No Adapter Found":
|
||||
state.append("critical")
|
||||
elif self._status == "On - 0":
|
||||
state.append("warning")
|
||||
elif "On" in self._status and not(self._status == "On - 0"):
|
||||
state.append("ON")
|
||||
else:
|
||||
state.append("critical")
|
||||
return state
|
||||
|
||||
def get_connected_devices(self):
|
||||
devices = 0
|
||||
objects = dbus.Interface(
|
||||
self._bus.get_object("org.bluez", "/"),
|
||||
"org.freedesktop.DBus.ObjectManager"
|
||||
).GetManagedObjects()
|
||||
for path, interfaces in objects.items():
|
||||
if "org.bluez.Device1" in interfaces:
|
||||
if dbus.Interface(
|
||||
self._bus.get_object("org.bluez", path),
|
||||
"org.freedesktop.DBus.Properties"
|
||||
).Get(
|
||||
"org.bluez.Device1", "Connected"
|
||||
):
|
||||
devices += 1
|
||||
return devices
|
|
@ -1,63 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the brightness of a display
|
||||
|
||||
Parameters:
|
||||
* brightness.step: The amount of increase/decrease on scroll in % (defaults to 2)
|
||||
* brightness.device_path: The device path (defaults to /sys/class/backlight/intel_backlight), can contain wildcards (in this case, the first matching path will be used)
|
||||
|
||||
"""
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
import glob
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.brightness))
|
||||
self._brightness = 0
|
||||
|
||||
self._device_path = self.find_device(self.parameter("device_path", "/sys/class/backlight/intel_backlight"))
|
||||
step = self.parameter("step", 2)
|
||||
|
||||
if bumblebee.util.which("light"):
|
||||
self.register_cmd(engine, "light -A {}%".format(step),
|
||||
"light -U {}%".format(step))
|
||||
elif bumblebee.util.which("brightnessctl"):
|
||||
self.register_cmd(engine, "brightnessctl s {}%+".format(step),
|
||||
"brightnessctl s {}%-".format(step))
|
||||
else:
|
||||
self.register_cmd(engine, "xbacklight +{}%".format(step),
|
||||
"xbacklight -{}%".format(step))
|
||||
|
||||
def find_device(self, device_path):
|
||||
res = glob.glob(device_path)
|
||||
if len(res) == 0:
|
||||
return device_path
|
||||
return res[0]
|
||||
|
||||
def register_cmd(self, engine, upCmd, downCmd):
|
||||
engine.input.register_callback(self, button=bumblebee.input.WHEEL_UP, cmd=upCmd)
|
||||
engine.input.register_callback(self, button=bumblebee.input.WHEEL_DOWN, cmd=downCmd)
|
||||
|
||||
def brightness(self, widget):
|
||||
if isinstance(self._brightness, float):
|
||||
return "{:3.0f}%".format(self._brightness).strip()
|
||||
else:
|
||||
return "n/a"
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
with open("{}/brightness".format(self._device_path)) as f:
|
||||
backlight = int(f.readline())
|
||||
with open("{}/max_brightness".format(self._device_path)) as f:
|
||||
max_brightness = int(f.readline())
|
||||
self._brightness = float(backlight * 100 / max_brightness)
|
||||
except:
|
||||
return "Error"
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,102 +0,0 @@
|
|||
#pylint: disable=C0111,R0903,W0212
|
||||
|
||||
"""Enable/disable automatic screen locking.
|
||||
|
||||
Requires the following executables:
|
||||
* xdg-screensaver
|
||||
* xdotool
|
||||
* xprop (as dependency for xdotool)
|
||||
* notify-send
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text="")
|
||||
)
|
||||
self._active = False
|
||||
self._xid = None
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self._toggle
|
||||
)
|
||||
|
||||
def _check_requirements(self):
|
||||
requirements = ['xdotool', 'xprop', 'xdg-screensaver']
|
||||
missing = []
|
||||
for tool in requirements:
|
||||
if not bumblebee.util.which(tool):
|
||||
missing.append(tool)
|
||||
return missing
|
||||
|
||||
def _get_i3bar_xid(self):
|
||||
xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip()
|
||||
if xid.isdigit():
|
||||
return xid
|
||||
logging.warning("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".")
|
||||
return None
|
||||
|
||||
def _notify(self):
|
||||
if not bumblebee.util.which('notify-send'):
|
||||
return
|
||||
|
||||
if self._active:
|
||||
bumblebee.util.execute("notify-send \"Consuming caffeine\"")
|
||||
else:
|
||||
bumblebee.util.execute("notify-send \"Out of coffee\"")
|
||||
|
||||
def _suspend_screensaver(self):
|
||||
self._xid = self._get_i3bar_xid()
|
||||
if self._xid is None:
|
||||
return False
|
||||
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
os.setsid()
|
||||
bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid))
|
||||
os._exit(0)
|
||||
else:
|
||||
os.waitpid(pid, 0)
|
||||
return True
|
||||
|
||||
def _resume_screensaver(self):
|
||||
success = True
|
||||
xprop_path = bumblebee.util.which('xprop')
|
||||
pids = [ p.pid for p in psutil.process_iter() if p.cmdline() == [xprop_path, '-id', str(self._xid), '-spy'] ]
|
||||
for pid in pids:
|
||||
try:
|
||||
os.kill(pid, 9)
|
||||
except OSError:
|
||||
success = False
|
||||
return success
|
||||
|
||||
def state(self, _):
|
||||
if self._active:
|
||||
return "activated"
|
||||
return "deactivated"
|
||||
|
||||
def _toggle(self, _):
|
||||
missing = self._check_requirements()
|
||||
if missing:
|
||||
logging.warning('Could not run caffeine - missing %s!', ", ".join(missing))
|
||||
return
|
||||
|
||||
self._active = not self._active
|
||||
if self._active:
|
||||
success = self._suspend_screensaver()
|
||||
else:
|
||||
success = self._resume_screensaver()
|
||||
|
||||
if success:
|
||||
self._notify()
|
||||
else:
|
||||
self._active = not self._active
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,42 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays CPU utilization across all CPUs.
|
||||
|
||||
Parameters:
|
||||
* cpu.warning : Warning threshold in % of CPU usage (defaults to 70%)
|
||||
* cpu.critical: Critical threshold in % of CPU usage (defaults to 80%)
|
||||
* cpu.format : Format string (defaults to "{:.01f}%")
|
||||
"""
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.utilization)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
widget.set("theme.minwidth", self._format.format(10.0-10e-20))
|
||||
self._utilization = psutil.cpu_percent(percpu=False)
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="gnome-system-monitor")
|
||||
|
||||
@property
|
||||
def _format(self):
|
||||
return self.parameter("format", "{:.01f}%")
|
||||
|
||||
def utilization(self, _):
|
||||
return self._format.format(self._utilization)
|
||||
|
||||
def update(self, widgets):
|
||||
self._utilization = psutil.cpu_percent(percpu=False)
|
||||
|
||||
def state(self, _):
|
||||
return self.threshold_state(self._utilization, 70, 80)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,156 +0,0 @@
|
|||
"""Multiwidget CPU module
|
||||
|
||||
Can display any combination of:
|
||||
|
||||
* max CPU frequency
|
||||
* total CPU load in percents (integer value)
|
||||
* per-core CPU load as graph - either mono or colored
|
||||
* CPU temperature (in Celsius degrees)
|
||||
* CPU fan speed
|
||||
|
||||
Requirements:
|
||||
|
||||
* the psutil Python module for the first three items from the list above
|
||||
* sensors executable for the rest
|
||||
|
||||
Parameters:
|
||||
* cpu2.layout: Space-separated list of widgets to add.
|
||||
Possible widgets are:
|
||||
* cpu2.maxfreq
|
||||
* cpu2.cpuload
|
||||
* cpu2.coresload
|
||||
* cpu2.temp
|
||||
* cpu2.fanspeed
|
||||
* cpu2.colored: 1 for colored per core load graph, 0 for mono (default)
|
||||
if this is set to 1, use --markup=pango
|
||||
* cpu2.temp_pattern: pattern to look for in the output of 'sensors -u';
|
||||
required if cpu2.temp widged is used
|
||||
* cpu2.fan_pattern: pattern to look for in the output of 'sensors -u';
|
||||
required if cpu2.fanspeed widged is used
|
||||
|
||||
Note: if you are getting "n/a" for CPU temperature / fan speed, then you're
|
||||
lacking the aforementioned pattern settings or they have wrong values.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
import bumblebee.output
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config, [])
|
||||
self._layout = self.parameter("layout", "cpu2.maxfreq cpu2.cpuload cpu2.coresload cpu2.temp cpu2.fanspeed")
|
||||
self._widget_names = self._layout.split()
|
||||
widget_list = []
|
||||
for widget_name in self._widget_names:
|
||||
if widget_name == "cpu2.maxfreq":
|
||||
widget = bumblebee.output.Widget(
|
||||
name=widget_name, full_text=self.maxfreq)
|
||||
widget.set("type", "freq")
|
||||
elif widget_name == "cpu2.cpuload":
|
||||
widget = bumblebee.output.Widget(
|
||||
name=widget_name, full_text=self.cpuload)
|
||||
widget.set("type", "load")
|
||||
elif widget_name == "cpu2.coresload":
|
||||
widget = bumblebee.output.Widget(
|
||||
name=widget_name, full_text=self.coresload)
|
||||
widget.set("type", "loads")
|
||||
elif widget_name == "cpu2.temp":
|
||||
widget = bumblebee.output.Widget(
|
||||
name=widget_name, full_text=self.temp)
|
||||
widget.set("type", "temp")
|
||||
elif widget_name == "cpu2.fanspeed":
|
||||
widget = bumblebee.output.Widget(
|
||||
name=widget_name, full_text=self.fanspeed)
|
||||
widget.set("type", "fan")
|
||||
widget_list.append(widget)
|
||||
self.widgets(widget_list)
|
||||
self._colored = bumblebee.util.asbool(self.parameter("colored", 0))
|
||||
self._temp_pattern = self.parameter("temp_pattern")
|
||||
if self._temp_pattern is None:
|
||||
self._temp = "n/a"
|
||||
self._fan_pattern = self.parameter("fan_pattern")
|
||||
if self._fan_pattern is None:
|
||||
self._fan = "n/a"
|
||||
# maxfreq is loaded only once at startup
|
||||
if "cpu2.maxfreq" in self._widget_names:
|
||||
self._maxfreq = psutil.cpu_freq().max / 1000
|
||||
self.update(widget_list)
|
||||
|
||||
def maxfreq(self, _):
|
||||
return "{:.2f}GHz".format(self._maxfreq)
|
||||
|
||||
def cpuload(self, _):
|
||||
return "{:>3}%".format(self._cpuload)
|
||||
|
||||
def add_color(self, bar):
|
||||
"""add color as pango markup to a bar"""
|
||||
if bar in ["▁", "▂"]:
|
||||
color = self.theme().color("green", "green")
|
||||
elif bar in ["▃", "▄"]:
|
||||
color = self.theme().color("yellow", "yellow")
|
||||
elif bar in ["▅", "▆"]:
|
||||
color = self.theme().color("orange", "orange")
|
||||
elif bar in ["▇", "█"]:
|
||||
color = self.theme().color("red", "red")
|
||||
colored_bar = "<span foreground='{}'>{}</span>".format(color, bar)
|
||||
return colored_bar
|
||||
|
||||
def coresload(self, _):
|
||||
mono_bars = [bumblebee.output.hbar(x) for x in self._coresload]
|
||||
if not self._colored:
|
||||
return "".join(mono_bars)
|
||||
colored_bars = [self.add_color(x) for x in mono_bars]
|
||||
return "".join(colored_bars)
|
||||
|
||||
def temp(self, _):
|
||||
if self._temp == "n/a" or self._temp == 0:
|
||||
return "n/a"
|
||||
return "{}°C".format(self._temp)
|
||||
|
||||
def fanspeed(self, _):
|
||||
if self._fanspeed == "n/a":
|
||||
return "n/a"
|
||||
return "{}RPM".format(self._fanspeed)
|
||||
|
||||
def _parse_sensors_output(self):
|
||||
output = bumblebee.util.execute("sensors -u")
|
||||
lines = output.split("\n")
|
||||
temp = "n/a"
|
||||
fan = "n/a"
|
||||
temp_line = None
|
||||
fan_line = None
|
||||
for line in lines:
|
||||
if self._temp_pattern is not None and self._temp_pattern in line:
|
||||
temp_line = line
|
||||
if self._fan_pattern is not None and self._fan_pattern in line:
|
||||
fan_line = line
|
||||
if temp_line is not None and fan_line is not None:
|
||||
break
|
||||
if temp_line is not None:
|
||||
temp = round(float(temp_line.split(":")[1].strip()))
|
||||
if fan_line is not None:
|
||||
fan = int(fan_line.split(":")[1].strip()[:-4])
|
||||
return temp, fan
|
||||
|
||||
def update(self, _):
|
||||
if "cpu2.maxfreq" in self._widget_names:
|
||||
self._maxfreq = psutil.cpu_freq().max / 1000
|
||||
if "cpu2.cpuload" in self._widget_names:
|
||||
self._cpuload = round(psutil.cpu_percent(percpu=False))
|
||||
if "cpu2.coresload" in self._widget_names:
|
||||
self._coresload = psutil.cpu_percent(percpu=True)
|
||||
if "cpu2.temp" in self._widget_names or "cpu2.fanspeed" in self._widget_names:
|
||||
self._temp, self._fanspeed = self._parse_sensors_output()
|
||||
|
||||
def state(self, widget):
|
||||
"""for having per-widget icons"""
|
||||
return [widget.get("type", "")]
|
|
@ -1,139 +0,0 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays currency exchange rates. Currently, displays currency between GBP and USD/EUR only.
|
||||
|
||||
Requires the following python packages:
|
||||
* requests
|
||||
|
||||
Parameters:
|
||||
* currency.interval: Interval in minutes between updates, default is 1.
|
||||
* currency.source: Source currency (ex. "GBP", "EUR"). Defaults to "auto", which infers the local one from IP address.
|
||||
* currency.destination: Comma-separated list of destination currencies (defaults to "USD,EUR")
|
||||
* currency.sourceformat: String format for source formatting; Defaults to "{}: {}" and has two variables,
|
||||
the base symbol and the rate list
|
||||
* currency.destinationdelimiter: Delimiter used for separating individual rates (defaults to "|")
|
||||
|
||||
Note: source and destination names right now must correspond to the names used by the API of https://markets.ft.com
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from babel.numbers import format_currency
|
||||
except ImportError:
|
||||
format_currency = None
|
||||
import json
|
||||
import os
|
||||
|
||||
SYMBOL = {
|
||||
"GBP": u"£", "EUR": u"€", "USD": u"$", "JPY": u"¥", "KRW": u"₩"
|
||||
}
|
||||
DEFAULT_DEST = "USD,EUR,auto"
|
||||
DEFAULT_SRC = "GBP"
|
||||
|
||||
API_URL = "https://markets.ft.com/data/currencies/ajax/conversion?baseCurrency={}&comparison={}"
|
||||
LOCATION_URL = "https://ipvigilante.com/"
|
||||
|
||||
|
||||
def get_local_country():
|
||||
r = requests.get(LOCATION_URL)
|
||||
location = r.json()
|
||||
return location['data']['country_name']
|
||||
|
||||
|
||||
def load_country_to_currency():
|
||||
fname = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
'data', 'country-by-currency-code.json')
|
||||
with open(fname, 'r') as f:
|
||||
data = json.load(f)
|
||||
country2curr = {}
|
||||
for dt in data:
|
||||
country2curr[dt['country']] = dt['currency_code']
|
||||
|
||||
return country2curr
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.price)
|
||||
)
|
||||
self._data = []
|
||||
self.interval_factor(60)
|
||||
self.interval(1)
|
||||
self._nextcheck = 0
|
||||
|
||||
src = self.parameter("source", DEFAULT_SRC)
|
||||
if src == "auto":
|
||||
self._base = self.find_local_currency()
|
||||
else:
|
||||
self._base = src
|
||||
|
||||
self._symbols = []
|
||||
for d in self.parameter("destination", DEFAULT_DEST).split(","):
|
||||
if d == 'auto':
|
||||
new = self.find_local_currency()
|
||||
else:
|
||||
new = d
|
||||
if new != self._base:
|
||||
self._symbols.append(new)
|
||||
|
||||
def price(self, widget):
|
||||
if len(self._data) == 0:
|
||||
return "?"
|
||||
|
||||
rates = []
|
||||
for sym, rate in self._data:
|
||||
rate_float = float(rate.replace(',',''))
|
||||
if format_currency:
|
||||
rates.append(format_currency(rate_float, sym))
|
||||
else:
|
||||
rate = self.fmt_rate(rate)
|
||||
rates.append(u"{}{}".format(rate, SYMBOL[sym] if sym in SYMBOL else sym))
|
||||
|
||||
basefmt = u"{}".format(self.parameter("sourceformat", "{}={}"))
|
||||
ratefmt = u"{}".format(self.parameter("destinationdelimiter", "="))
|
||||
|
||||
if format_currency:
|
||||
base_val = format_currency(1, self._base)
|
||||
else:
|
||||
base_val = '1{}'.format(SYMBOL[self._base] if self._base in SYMBOL else self._base)
|
||||
|
||||
return basefmt.format(base_val, ratefmt.join(rates))
|
||||
|
||||
def update(self, widgets):
|
||||
self._data = []
|
||||
for symbol in self._symbols:
|
||||
url = API_URL.format(self._base, symbol)
|
||||
try:
|
||||
response = requests.get(url).json()
|
||||
self._data.append((symbol, response['data']['exchangeRate']))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def find_local_currency(self):
|
||||
'''Use geolocation lookup to find local currency'''
|
||||
try:
|
||||
country = get_local_country()
|
||||
currency_map = load_country_to_currency()
|
||||
return currency_map.get(country, DEFAULT_SRC)
|
||||
except:
|
||||
return DEFAULT_SRC
|
||||
|
||||
def fmt_rate(self, rate):
|
||||
float_rate = float(rate.replace(',', ''))
|
||||
if not 0.01 < float_rate < 100:
|
||||
ret = rate
|
||||
else:
|
||||
ret = "%.3g" % float_rate
|
||||
|
||||
return ret
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,974 +0,0 @@
|
|||
[
|
||||
{
|
||||
"country": "Afghanistan",
|
||||
"currency_code": "AFN"
|
||||
},
|
||||
{
|
||||
"country": "Albania",
|
||||
"currency_code": "ALL"
|
||||
},
|
||||
{
|
||||
"country": "Algeria",
|
||||
"currency_code": "DZD"
|
||||
},
|
||||
{
|
||||
"country": "American Samoa",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Andorra",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Angola",
|
||||
"currency_code": "AOA"
|
||||
},
|
||||
{
|
||||
"country": "Anguilla",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Antarctica",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Antigua and Barbuda",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Argentina",
|
||||
"currency_code": "ARS"
|
||||
},
|
||||
{
|
||||
"country": "Armenia",
|
||||
"currency_code": "AMD"
|
||||
},
|
||||
{
|
||||
"country": "Aruba",
|
||||
"currency_code": "AWG"
|
||||
},
|
||||
{
|
||||
"country": "Australia",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Austria",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Azerbaijan",
|
||||
"currency_code": "AZN"
|
||||
},
|
||||
{
|
||||
"country": "Bahamas",
|
||||
"currency_code": "BSD"
|
||||
},
|
||||
{
|
||||
"country": "Bahrain",
|
||||
"currency_code": "BHD"
|
||||
},
|
||||
{
|
||||
"country": "Bangladesh",
|
||||
"currency_code": "BDT"
|
||||
},
|
||||
{
|
||||
"country": "Barbados",
|
||||
"currency_code": "BBD"
|
||||
},
|
||||
{
|
||||
"country": "Belarus",
|
||||
"currency_code": "BYR"
|
||||
},
|
||||
{
|
||||
"country": "Belgium",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Belize",
|
||||
"currency_code": "BZD"
|
||||
},
|
||||
{
|
||||
"country": "Benin",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Bermuda",
|
||||
"currency_code": "BMD"
|
||||
},
|
||||
{
|
||||
"country": "Bhutan",
|
||||
"currency_code": "BTN"
|
||||
},
|
||||
{
|
||||
"country": "Bolivia",
|
||||
"currency_code": "BOB"
|
||||
},
|
||||
{
|
||||
"country": "Bosnia and Herzegovina",
|
||||
"currency_code": "BAM"
|
||||
},
|
||||
{
|
||||
"country": "Botswana",
|
||||
"currency_code": "BWP"
|
||||
},
|
||||
{
|
||||
"country": "Bouvet Island",
|
||||
"currency_code": "NOK"
|
||||
},
|
||||
{
|
||||
"country": "Brazil",
|
||||
"currency_code": "BRL"
|
||||
},
|
||||
{
|
||||
"country": "British Indian Ocean Territory",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Brunei",
|
||||
"currency_code": "BND"
|
||||
},
|
||||
{
|
||||
"country": "Bulgaria",
|
||||
"currency_code": "BGN"
|
||||
},
|
||||
{
|
||||
"country": "Burkina Faso",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Burundi",
|
||||
"currency_code": "BIF"
|
||||
},
|
||||
{
|
||||
"country": "Cambodia",
|
||||
"currency_code": "KHR"
|
||||
},
|
||||
{
|
||||
"country": "Cameroon",
|
||||
"currency_code": "XAF"
|
||||
},
|
||||
{
|
||||
"country": "Canada",
|
||||
"currency_code": "CAD"
|
||||
},
|
||||
{
|
||||
"country": "Cape Verde",
|
||||
"currency_code": "CVE"
|
||||
},
|
||||
{
|
||||
"country": "Cayman Islands",
|
||||
"currency_code": "KYD"
|
||||
},
|
||||
{
|
||||
"country": "Central African Republic",
|
||||
"currency_code": "XAF"
|
||||
},
|
||||
{
|
||||
"country": "Chad",
|
||||
"currency_code": "XAF"
|
||||
},
|
||||
{
|
||||
"country": "Chile",
|
||||
"currency_code": "CLP"
|
||||
},
|
||||
{
|
||||
"country": "China",
|
||||
"currency_code": "CNY"
|
||||
},
|
||||
{
|
||||
"country": "Christmas Island",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Cocos (Keeling) Islands",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Colombia",
|
||||
"currency_code": "COP"
|
||||
},
|
||||
{
|
||||
"country": "Comoros",
|
||||
"currency_code": "KMF"
|
||||
},
|
||||
{
|
||||
"country": "Congo",
|
||||
"currency_code": "XAF"
|
||||
},
|
||||
{
|
||||
"country": "Cook Islands",
|
||||
"currency_code": "NZD"
|
||||
},
|
||||
{
|
||||
"country": "Costa Rica",
|
||||
"currency_code": "CRC"
|
||||
},
|
||||
{
|
||||
"country": "Croatia",
|
||||
"currency_code": "HRK"
|
||||
},
|
||||
{
|
||||
"country": "Cuba",
|
||||
"currency_code": "CUP"
|
||||
},
|
||||
{
|
||||
"country": "Cyprus",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Czech Republic",
|
||||
"currency_code": "CZK"
|
||||
},
|
||||
{
|
||||
"country": "Denmark",
|
||||
"currency_code": "DKK"
|
||||
},
|
||||
{
|
||||
"country": "Djibouti",
|
||||
"currency_code": "DJF"
|
||||
},
|
||||
{
|
||||
"country": "Dominica",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Dominican Republic",
|
||||
"currency_code": "DOP"
|
||||
},
|
||||
{
|
||||
"country": "East Timor",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Ecuador",
|
||||
"currency_code": "ECS"
|
||||
},
|
||||
{
|
||||
"country": "Egypt",
|
||||
"currency_code": "EGP"
|
||||
},
|
||||
{
|
||||
"country": "El Salvador",
|
||||
"currency_code": "SVC"
|
||||
},
|
||||
{
|
||||
"country": "England",
|
||||
"currency_code": "GBP"
|
||||
},
|
||||
{
|
||||
"country": "Equatorial Guinea",
|
||||
"currency_code": "XAF"
|
||||
},
|
||||
{
|
||||
"country": "Eritrea",
|
||||
"currency_code": "ERN"
|
||||
},
|
||||
{
|
||||
"country": "Estonia",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Ethiopia",
|
||||
"currency_code": "ETB"
|
||||
},
|
||||
{
|
||||
"country": "Falkland Islands",
|
||||
"currency_code": "FKP"
|
||||
},
|
||||
{
|
||||
"country": "Faroe Islands",
|
||||
"currency_code": "DKK"
|
||||
},
|
||||
{
|
||||
"country": "Fiji Islands",
|
||||
"currency_code": "FJD"
|
||||
},
|
||||
{
|
||||
"country": "Finland",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "France",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "French Guiana",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "French Polynesia",
|
||||
"currency_code": "XPF"
|
||||
},
|
||||
{
|
||||
"country": "French Southern territories",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Gabon",
|
||||
"currency_code": "XAF"
|
||||
},
|
||||
{
|
||||
"country": "Gambia",
|
||||
"currency_code": "GMD"
|
||||
},
|
||||
{
|
||||
"country": "Georgia",
|
||||
"currency_code": "GEL"
|
||||
},
|
||||
{
|
||||
"country": "Germany",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Ghana",
|
||||
"currency_code": "GHS"
|
||||
},
|
||||
{
|
||||
"country": "Gibraltar",
|
||||
"currency_code": "GIP"
|
||||
},
|
||||
{
|
||||
"country": "Greece",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Greenland",
|
||||
"currency_code": "DKK"
|
||||
},
|
||||
{
|
||||
"country": "Grenada",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Guadeloupe",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Guam",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Guatemala",
|
||||
"currency_code": "QTQ"
|
||||
},
|
||||
{
|
||||
"country": "Guinea",
|
||||
"currency_code": "GNF"
|
||||
},
|
||||
{
|
||||
"country": "Guinea-Bissau",
|
||||
"currency_code": "CFA"
|
||||
},
|
||||
{
|
||||
"country": "Guyana",
|
||||
"currency_code": "GYD"
|
||||
},
|
||||
{
|
||||
"country": "Haiti",
|
||||
"currency_code": "HTG"
|
||||
},
|
||||
{
|
||||
"country": "Heard Island and McDonald Islands",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Holy See (Vatican City State)",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Honduras",
|
||||
"currency_code": "HNL"
|
||||
},
|
||||
{
|
||||
"country": "Hong Kong",
|
||||
"currency_code": "HKD"
|
||||
},
|
||||
{
|
||||
"country": "Hungary",
|
||||
"currency_code": "HUF"
|
||||
},
|
||||
{
|
||||
"country": "Iceland",
|
||||
"currency_code": "ISK"
|
||||
},
|
||||
{
|
||||
"country": "India",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
{
|
||||
"country": "Indonesia",
|
||||
"currency_code": "IDR"
|
||||
},
|
||||
{
|
||||
"country": "Iran",
|
||||
"currency_code": "IRR"
|
||||
},
|
||||
{
|
||||
"country": "Iraq",
|
||||
"currency_code": "IQD"
|
||||
},
|
||||
{
|
||||
"country": "Ireland",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Israel",
|
||||
"currency_code": "ILS"
|
||||
},
|
||||
{
|
||||
"country": "Italy",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Ivory Coast",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Jamaica",
|
||||
"currency_code": "JMD"
|
||||
},
|
||||
{
|
||||
"country": "Japan",
|
||||
"currency_code": "JPY"
|
||||
},
|
||||
{
|
||||
"country": "Jordan",
|
||||
"currency_code": "JOD"
|
||||
},
|
||||
{
|
||||
"country": "Kazakhstan",
|
||||
"currency_code": "KZT"
|
||||
},
|
||||
{
|
||||
"country": "Kenya",
|
||||
"currency_code": "KES"
|
||||
},
|
||||
{
|
||||
"country": "Kiribati",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Kuwait",
|
||||
"currency_code": "KWD"
|
||||
},
|
||||
{
|
||||
"country": "Kyrgyzstan",
|
||||
"currency_code": "KGS"
|
||||
},
|
||||
{
|
||||
"country": "Laos",
|
||||
"currency_code": "LAK"
|
||||
},
|
||||
{
|
||||
"country": "Latvia",
|
||||
"currency_code": "LVL"
|
||||
},
|
||||
{
|
||||
"country": "Lebanon",
|
||||
"currency_code": "LBP"
|
||||
},
|
||||
{
|
||||
"country": "Lesotho",
|
||||
"currency_code": "LSL"
|
||||
},
|
||||
{
|
||||
"country": "Liberia",
|
||||
"currency_code": "LRD"
|
||||
},
|
||||
{
|
||||
"country": "Libyan Arab Jamahiriya",
|
||||
"currency_code": "LYD"
|
||||
},
|
||||
{
|
||||
"country": "Liechtenstein",
|
||||
"currency_code": "CHF"
|
||||
},
|
||||
{
|
||||
"country": "Lithuania",
|
||||
"currency_code": "LTL"
|
||||
},
|
||||
{
|
||||
"country": "Luxembourg",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Macao",
|
||||
"currency_code": "MOP"
|
||||
},
|
||||
{
|
||||
"country": "North Macedonia",
|
||||
"currency_code": "MKD"
|
||||
},
|
||||
{
|
||||
"country": "Madagascar",
|
||||
"currency_code": "MGF"
|
||||
},
|
||||
{
|
||||
"country": "Malawi",
|
||||
"currency_code": "MWK"
|
||||
},
|
||||
{
|
||||
"country": "Malaysia",
|
||||
"currency_code": "MYR"
|
||||
},
|
||||
{
|
||||
"country": "Maldives",
|
||||
"currency_code": "MVR"
|
||||
},
|
||||
{
|
||||
"country": "Mali",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Malta",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Marshall Islands",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Martinique",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Mauritania",
|
||||
"currency_code": "MRO"
|
||||
},
|
||||
{
|
||||
"country": "Mauritius",
|
||||
"currency_code": "MUR"
|
||||
},
|
||||
{
|
||||
"country": "Mayotte",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Mexico",
|
||||
"currency_code": "MXN"
|
||||
},
|
||||
{
|
||||
"country": "Micronesia, Federated States of",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Moldova",
|
||||
"currency_code": "MDL"
|
||||
},
|
||||
{
|
||||
"country": "Monaco",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Mongolia",
|
||||
"currency_code": "MNT"
|
||||
},
|
||||
{
|
||||
"country": "Montserrat",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Morocco",
|
||||
"currency_code": "MAD"
|
||||
},
|
||||
{
|
||||
"country": "Mozambique",
|
||||
"currency_code": "MZN"
|
||||
},
|
||||
{
|
||||
"country": "Myanmar",
|
||||
"currency_code": "MMR"
|
||||
},
|
||||
{
|
||||
"country": "Namibia",
|
||||
"currency_code": "NAD"
|
||||
},
|
||||
{
|
||||
"country": "Nauru",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Nepal",
|
||||
"currency_code": "NPR"
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Netherlands Antilles",
|
||||
"currency_code": "ANG"
|
||||
},
|
||||
{
|
||||
"country": "New Caledonia",
|
||||
"currency_code": "XPF"
|
||||
},
|
||||
{
|
||||
"country": "New Zealand",
|
||||
"currency_code": "NZD"
|
||||
},
|
||||
{
|
||||
"country": "Nicaragua",
|
||||
"currency_code": "NIO"
|
||||
},
|
||||
{
|
||||
"country": "Niger",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Nigeria",
|
||||
"currency_code": "NGN"
|
||||
},
|
||||
{
|
||||
"country": "Niue",
|
||||
"currency_code": "NZD"
|
||||
},
|
||||
{
|
||||
"country": "Norfolk Island",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "North Korea",
|
||||
"currency_code": "KPW"
|
||||
},
|
||||
{
|
||||
"country": "Northern Ireland",
|
||||
"currency_code": "GBP"
|
||||
},
|
||||
{
|
||||
"country": "Northern Mariana Islands",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Norway",
|
||||
"currency_code": "NOK"
|
||||
},
|
||||
{
|
||||
"country": "Oman",
|
||||
"currency_code": "OMR"
|
||||
},
|
||||
{
|
||||
"country": "Pakistan",
|
||||
"currency_code": "PKR"
|
||||
},
|
||||
{
|
||||
"country": "Palau",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Palestine",
|
||||
"currency_code": null
|
||||
},
|
||||
{
|
||||
"country": "Panama",
|
||||
"currency_code": "PAB"
|
||||
},
|
||||
{
|
||||
"country": "Papua New Guinea",
|
||||
"currency_code": "PGK"
|
||||
},
|
||||
{
|
||||
"country": "Paraguay",
|
||||
"currency_code": "PYG"
|
||||
},
|
||||
{
|
||||
"country": "Peru",
|
||||
"currency_code": "PEN"
|
||||
},
|
||||
{
|
||||
"country": "Philippines",
|
||||
"currency_code": "PHP"
|
||||
},
|
||||
{
|
||||
"country": "Pitcairn",
|
||||
"currency_code": "NZD"
|
||||
},
|
||||
{
|
||||
"country": "Poland",
|
||||
"currency_code": "PLN"
|
||||
},
|
||||
{
|
||||
"country": "Portugal",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Puerto Rico",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Qatar",
|
||||
"currency_code": "QAR"
|
||||
},
|
||||
{
|
||||
"country": "Reunion",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Romania",
|
||||
"currency_code": "RON"
|
||||
},
|
||||
{
|
||||
"country": "Russian Federation",
|
||||
"currency_code": "RUB"
|
||||
},
|
||||
{
|
||||
"country": "Rwanda",
|
||||
"currency_code": "RWF"
|
||||
},
|
||||
{
|
||||
"country": "Saint Helena",
|
||||
"currency_code": "SHP"
|
||||
},
|
||||
{
|
||||
"country": "Saint Kitts and Nevis",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Saint Lucia",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Saint Pierre and Miquelon",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Saint Vincent and the Grenadines",
|
||||
"currency_code": "XCD"
|
||||
},
|
||||
{
|
||||
"country": "Samoa",
|
||||
"currency_code": "WST"
|
||||
},
|
||||
{
|
||||
"country": "San Marino",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Sao Tome and Principe",
|
||||
"currency_code": "STD"
|
||||
},
|
||||
{
|
||||
"country": "Saudi Arabia",
|
||||
"currency_code": "SAR"
|
||||
},
|
||||
{
|
||||
"country": "Scotland",
|
||||
"currency_code": "GBP"
|
||||
},
|
||||
{
|
||||
"country": "Senegal",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Seychelles",
|
||||
"currency_code": "SCR"
|
||||
},
|
||||
{
|
||||
"country": "Sierra Leone",
|
||||
"currency_code": "SLL"
|
||||
},
|
||||
{
|
||||
"country": "Singapore",
|
||||
"currency_code": "SGD"
|
||||
},
|
||||
{
|
||||
"country": "Slovakia",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Slovenia",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "Solomon Islands",
|
||||
"currency_code": "SBD"
|
||||
},
|
||||
{
|
||||
"country": "Somalia",
|
||||
"currency_code": "SOS"
|
||||
},
|
||||
{
|
||||
"country": "South Africa",
|
||||
"currency_code": "ZAR"
|
||||
},
|
||||
{
|
||||
"country": "South Georgia and the South Sandwich Islands",
|
||||
"currency_code": "GBP"
|
||||
},
|
||||
{
|
||||
"country": "South Korea",
|
||||
"currency_code": "KRW"
|
||||
},
|
||||
{
|
||||
"country": "South Sudan",
|
||||
"currency_code": "SSP"
|
||||
},
|
||||
{
|
||||
"country": "Spain",
|
||||
"currency_code": "EUR"
|
||||
},
|
||||
{
|
||||
"country": "SriLanka",
|
||||
"currency_code": "LKR"
|
||||
},
|
||||
{
|
||||
"country": "Sudan",
|
||||
"currency_code": "SDG"
|
||||
},
|
||||
{
|
||||
"country": "Suriname",
|
||||
"currency_code": "SRD"
|
||||
},
|
||||
{
|
||||
"country": "Svalbard and Jan Mayen",
|
||||
"currency_code": "NOK"
|
||||
},
|
||||
{
|
||||
"country": "Swaziland",
|
||||
"currency_code": "SZL"
|
||||
},
|
||||
{
|
||||
"country": "Sweden",
|
||||
"currency_code": "SEK"
|
||||
},
|
||||
{
|
||||
"country": "Switzerland",
|
||||
"currency_code": "CHF"
|
||||
},
|
||||
{
|
||||
"country": "Syria",
|
||||
"currency_code": "SYP"
|
||||
},
|
||||
{
|
||||
"country": "Tajikistan",
|
||||
"currency_code": "TJS"
|
||||
},
|
||||
{
|
||||
"country": "Tanzania",
|
||||
"currency_code": "TZS"
|
||||
},
|
||||
{
|
||||
"country": "Thailand",
|
||||
"currency_code": "THB"
|
||||
},
|
||||
{
|
||||
"country": "The Democratic Republic of Congo",
|
||||
"currency_code": "CDF"
|
||||
},
|
||||
{
|
||||
"country": "Togo",
|
||||
"currency_code": "XOF"
|
||||
},
|
||||
{
|
||||
"country": "Tokelau",
|
||||
"currency_code": "NZD"
|
||||
},
|
||||
{
|
||||
"country": "Tonga",
|
||||
"currency_code": "TOP"
|
||||
},
|
||||
{
|
||||
"country": "Trinidad and Tobago",
|
||||
"currency_code": "TTD"
|
||||
},
|
||||
{
|
||||
"country": "Tunisia",
|
||||
"currency_code": "TND"
|
||||
},
|
||||
{
|
||||
"country": "Turkey",
|
||||
"currency_code": "TRY"
|
||||
},
|
||||
{
|
||||
"country": "Turkmenistan",
|
||||
"currency_code": "TMT"
|
||||
},
|
||||
{
|
||||
"country": "Turks and Caicos Islands",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Tuvalu",
|
||||
"currency_code": "AUD"
|
||||
},
|
||||
{
|
||||
"country": "Uganda",
|
||||
"currency_code": "UGX"
|
||||
},
|
||||
{
|
||||
"country": "Ukraine",
|
||||
"currency_code": "UAH"
|
||||
},
|
||||
{
|
||||
"country": "United Arab Emirates",
|
||||
"currency_code": "AED"
|
||||
},
|
||||
{
|
||||
"country": "United Kingdom",
|
||||
"currency_code": "GBP"
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "United States Minor Outlying Islands",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Uruguay",
|
||||
"currency_code": "UYU"
|
||||
},
|
||||
{
|
||||
"country": "Uzbekistan",
|
||||
"currency_code": "UZS"
|
||||
},
|
||||
{
|
||||
"country": "Vanuatu",
|
||||
"currency_code": "VUV"
|
||||
},
|
||||
{
|
||||
"country": "Venezuela",
|
||||
"currency_code": "VEF"
|
||||
},
|
||||
{
|
||||
"country": "Vietnam",
|
||||
"currency_code": "VND"
|
||||
},
|
||||
{
|
||||
"country": "Virgin Islands, British",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Virgin Islands, U.S.",
|
||||
"currency_code": "USD"
|
||||
},
|
||||
{
|
||||
"country": "Wales",
|
||||
"currency_code": "GBP"
|
||||
},
|
||||
{
|
||||
"country": "Wallis and Futuna",
|
||||
"currency_code": "XPF"
|
||||
},
|
||||
{
|
||||
"country": "Western Sahara",
|
||||
"currency_code": "MAD"
|
||||
},
|
||||
{
|
||||
"country": "Yemen",
|
||||
"currency_code": "YER"
|
||||
},
|
||||
{
|
||||
"country": "Yugoslavia",
|
||||
"currency_code": null
|
||||
},
|
||||
{
|
||||
"country": "Zambia",
|
||||
"currency_code": "ZMW"
|
||||
},
|
||||
{
|
||||
"country": "Zimbabwe",
|
||||
"currency_code": "ZWD"
|
||||
}
|
||||
]
|
|
@ -1,50 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current date and time.
|
||||
|
||||
Parameters:
|
||||
* datetime.format: strftime()-compatible formatting string
|
||||
* date.format : alias for datetime.format
|
||||
* time.format : alias for datetime.format
|
||||
* datetime.locale: locale to use rather than the system default
|
||||
* date.locale : alias for datetime.locale
|
||||
* time.locale : alias for datetime.locale
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import datetime
|
||||
import locale
|
||||
import bumblebee.engine
|
||||
|
||||
def default_format(module):
|
||||
default = "%x %X"
|
||||
if module == "date":
|
||||
default = "%x"
|
||||
if module == "time":
|
||||
default = "%X"
|
||||
return default
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.get_time))
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="calendar")
|
||||
self._fmt = self.parameter("format", default_format(self.name))
|
||||
l = locale.getdefaultlocale()
|
||||
if not l or l == (None, None):
|
||||
l = ('en_US', 'UTF-8')
|
||||
lcl = self.parameter("locale", ".".join(l))
|
||||
try:
|
||||
locale.setlocale(locale.LC_TIME, lcl.split("."))
|
||||
except Exception as e:
|
||||
locale.setlocale(locale.LC_TIME, ('en_US', 'UTF-8'))
|
||||
|
||||
def get_time(self, widget):
|
||||
enc = locale.getpreferredencoding()
|
||||
retval = datetime.datetime.now().strftime(self._fmt)
|
||||
if hasattr(retval, "decode"):
|
||||
return retval.decode(enc)
|
||||
return retval
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,143 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current song being played in DeaDBeeF and provides
|
||||
some media control bindings.
|
||||
Left click toggles pause, scroll up skips the current song, scroll
|
||||
down returns to the previous song.
|
||||
|
||||
Requires the following library:
|
||||
* subprocess
|
||||
Parameters:
|
||||
* deadbeef.format: Format string (defaults to "{artist} - {title}")
|
||||
Available values are: {artist}, {title}, {album}, {length},
|
||||
{trackno}, {year}, {comment},
|
||||
{copyright}, {time}
|
||||
This is deprecated, but much simpler.
|
||||
* deadbeef.tf_format: A foobar2000 title formatting-style format string.
|
||||
These can be much more sophisticated than the standard
|
||||
format strings. This is off by default, but specifying
|
||||
any tf_format will enable it. If both deadbeef.format
|
||||
and deadbeef.tf_format are specified, deadbeef.tf_format
|
||||
takes priority.
|
||||
* deadbeef.tf_format_if_stopped: Controls whether or not the tf_format format
|
||||
string should be displayed even if no song is paused or
|
||||
playing. This could be useful if you want to implement
|
||||
your own stop strings with the built in logic. Any non-
|
||||
null value will enable this (by default the module will
|
||||
hide itself when the player is stopped).
|
||||
* deadbeef.previous: Change binding for previous song (default is left click)
|
||||
* deadbeef.next: Change binding for next song (default is right click)
|
||||
* deadbeef.pause: Change binding for toggling pause (default is middle click)
|
||||
Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are:
|
||||
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
|
||||
|
||||
"""
|
||||
import sys
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from bumblebee.output import scrollable
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.deadbeef)
|
||||
)
|
||||
buttons = {"LEFT_CLICK": bumblebee.input.LEFT_MOUSE,
|
||||
"RIGHT_CLICK": bumblebee.input.RIGHT_MOUSE,
|
||||
"MIDDLE_CLICK": bumblebee.input.MIDDLE_MOUSE,
|
||||
"SCROLL_UP": bumblebee.input.WHEEL_UP,
|
||||
"SCROLL_DOWN": bumblebee.input.WHEEL_DOWN,
|
||||
}
|
||||
|
||||
self._song = ""
|
||||
self._format = self.parameter("format", "{artist} - {title}")
|
||||
self._tf_format = self.parameter("tf_format", "")
|
||||
self._show_tf_when_stopped = bool(self.parameter("tf_format_if_stopped", ""))
|
||||
prev_button = self.parameter("previous", "LEFT_CLICK")
|
||||
next_button = self.parameter("next", "RIGHT_CLICK")
|
||||
pause_button = self.parameter("pause", "MIDDLE_CLICK")
|
||||
|
||||
self.now_playing = ["deadbeef", "--nowplaying", "%a;%t;%b;%l;%n;%y;%c;%r;%e"]
|
||||
self.now_playing_tf = ["deadbeef", "--nowplaying-tf", ""]
|
||||
cmd = "deadbeef "
|
||||
|
||||
engine.input.register_callback(self, button=buttons[prev_button],
|
||||
cmd=cmd + "--prev")
|
||||
engine.input.register_callback(self, button=buttons[next_button],
|
||||
cmd=cmd + "--next")
|
||||
engine.input.register_callback(self, button=buttons[pause_button],
|
||||
cmd=cmd + "--play-pause")
|
||||
|
||||
# modify the tf_format if we don't want it to show on stop
|
||||
# this adds conditions to the query itself, rather than
|
||||
# polling to see if deadbeef is running
|
||||
# doing this reduces the number of calls we have to make
|
||||
if self._tf_format and not self._show_tf_when_stopped:
|
||||
self._tf_format = "$if($or(%isplaying%,%ispaused%),{query})".format(query=self._tf_format)
|
||||
|
||||
@scrollable
|
||||
def deadbeef(self, widget):
|
||||
return self.string_song
|
||||
|
||||
def hidden(self):
|
||||
return self.string_song == ""
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
if self._tf_format == "": # no tf format set, use the old style
|
||||
return self.update_standard(widgets)
|
||||
return self.update_tf(widgets)
|
||||
except Exception:
|
||||
self._song = "error"
|
||||
|
||||
def update_tf(self, widgets):
|
||||
## ensure that deadbeef is actually running
|
||||
## easiest way to do this is to check --nowplaying for
|
||||
## the string "nothing"
|
||||
if read_process(self.now_playing) == "nothing":
|
||||
self._song = ""
|
||||
return
|
||||
## perform the actual query -- these can be much more sophisticated
|
||||
self.now_playing_tf[-1] = self._tf_format
|
||||
data = read_process(self.now_playing_tf)
|
||||
self._song = data
|
||||
|
||||
def update_standard(self, widgets):
|
||||
data = read_process(self.now_playing)
|
||||
if data == "nothing":
|
||||
self._song = ""
|
||||
else:
|
||||
data = data.split(";")
|
||||
self._song = self._format.format(artist=data[0],
|
||||
title=data[1],
|
||||
album=data[2],
|
||||
length=data[3],
|
||||
trackno=data[4],
|
||||
year=data[5],
|
||||
comment=data[6],
|
||||
copyright=data[7],
|
||||
time=data[8])
|
||||
|
||||
@property
|
||||
def string_song(self):
|
||||
"""\
|
||||
Returns the current song as a string, either as a unicode() (Python <
|
||||
3) or a regular str() (Python >= 3)
|
||||
"""
|
||||
if sys.version_info.major < 3:
|
||||
return unicode(self._song)
|
||||
return str(self._song)
|
||||
|
||||
def read_process(command):
|
||||
proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
return proc.stdout.read()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,77 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current song being played
|
||||
Requires the following library:
|
||||
* python-dbus
|
||||
Parameters:
|
||||
* deezer.format: Format string (defaults to "{artist} - {title}")
|
||||
Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus}
|
||||
* deezer.previous: Change binding for previous song (default is left click)
|
||||
* deezer.next: Change binding for next song (default is right click)
|
||||
* deezer.pause: Change binding for toggling pause (default is middle click)
|
||||
Available options for deezer.previous, deezer.next and deezer.pause are:
|
||||
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from bumblebee.output import scrollable
|
||||
|
||||
try:
|
||||
import dbus
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.deezer)
|
||||
)
|
||||
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE,
|
||||
"RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE,
|
||||
"MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE,
|
||||
"SCROLL_UP":bumblebee.input.WHEEL_UP,
|
||||
"SCROLL_DOWN":bumblebee.input.WHEEL_DOWN,
|
||||
}
|
||||
|
||||
self._song = ""
|
||||
self._format = self.parameter("format", "{artist} - {title}")
|
||||
prev_button = self.parameter("previous", "LEFT_CLICK")
|
||||
next_button = self.parameter("next", "RIGHT_CLICK")
|
||||
pause_button = self.parameter("pause", "MIDDLE_CLICK")
|
||||
|
||||
cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.deezer \
|
||||
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player."
|
||||
engine.input.register_callback(self, button=buttons[prev_button],
|
||||
cmd=cmd + "Previous")
|
||||
engine.input.register_callback(self, button=buttons[next_button],
|
||||
cmd=cmd + "Next")
|
||||
engine.input.register_callback(self, button=buttons[pause_button],
|
||||
cmd=cmd + "PlayPause")
|
||||
|
||||
## @scrollable
|
||||
def deezer(self, widget):
|
||||
return str(self._song)
|
||||
|
||||
def hidden(self):
|
||||
return str(self._song) == ""
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
bus = dbus.SessionBus()
|
||||
deezer = bus.get_object("org.mpris.MediaPlayer2.deezer", "/org/mpris/MediaPlayer2")
|
||||
deezer_iface = dbus.Interface(deezer, 'org.freedesktop.DBus.Properties')
|
||||
props = deezer_iface.Get('org.mpris.MediaPlayer2.Player', 'Metadata')
|
||||
playback_status = str(deezer_iface.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus'))
|
||||
self._song = self._format.format(album=str(props.get('xesam:album')),
|
||||
title=str(props.get('xesam:title')),
|
||||
artist=','.join(props.get('xesam:artist')),
|
||||
trackNumber=str(props.get('xesam:trackNumber')),
|
||||
playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",)
|
||||
except Exception:
|
||||
self._song = ""
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,88 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Shows free diskspace, total diskspace and the percentage of free disk space.
|
||||
|
||||
Parameters:
|
||||
* disk.warning: Warning threshold in % of disk space (defaults to 80%)
|
||||
* disk.critical: Critical threshold in % of disk space (defaults ot 90%)
|
||||
* disk.path: Path to calculate disk usage from (defaults to /)
|
||||
* disk.open: Which application / file manager to launch (default xdg-open)
|
||||
* disk.format: Format string, tags {path}, {used}, {left}, {size} and {percent} (defaults to "{path} {used}/{size} ({percent:05.02f}%)")
|
||||
* (deprecated) disk.showUsed: Show used space (defaults to yes)
|
||||
* (deprecated) disk.showSize: Show total size (defaults to yes)
|
||||
* (deprecated) disk.showPercent: Show usage percentage (defaults to yes)
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.diskspace)
|
||||
)
|
||||
self._path = self.parameter("path", "/")
|
||||
self._format = self.parameter("format", "{used}/{size} ({percent:05.02f}%)")
|
||||
self._app = self.parameter("open", "xdg-open")
|
||||
|
||||
self._used = 0
|
||||
self._left = 0
|
||||
self._size = 0
|
||||
self._percent = 0
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="{} {}".format(self._app,
|
||||
self._path))
|
||||
|
||||
|
||||
def diskspace(self, widget):
|
||||
used_str = bumblebee.util.bytefmt(self._used)
|
||||
size_str = bumblebee.util.bytefmt(self._size)
|
||||
left_str = bumblebee.util.bytefmt(self._left)
|
||||
percent_str = self._percent
|
||||
|
||||
sused = self.has_parameter("showUsed")
|
||||
ssize = self.has_parameter("showSize")
|
||||
spercent = self.has_parameter("showPercent")
|
||||
|
||||
if all(not param for param in (sused, ssize, spercent)):
|
||||
return self._format.format(path=self._path,
|
||||
used=used_str,
|
||||
left=left_str,
|
||||
size=size_str,
|
||||
percent=percent_str)
|
||||
else:
|
||||
rv = ""
|
||||
sused = bumblebee.util.asbool(self.parameter("showUsed", True))
|
||||
ssize = bumblebee.util.asbool(self.parameter("showSize", True))
|
||||
spercent = bumblebee.util.asbool(self.parameter("showPercent", True))
|
||||
|
||||
if sused:
|
||||
rv = "{}{}".format(rv, used_str)
|
||||
if sused and ssize:
|
||||
rv = "{}/".format(rv)
|
||||
if ssize:
|
||||
rv = "{}{}".format(rv, size_str)
|
||||
if spercent:
|
||||
if not sused and not ssize:
|
||||
rv = "{:05.02f}%".format(percent_str)
|
||||
else:
|
||||
rv = "{} ({:05.02f}%)".format(rv, percent_str)
|
||||
return rv
|
||||
|
||||
|
||||
def update(self, widgets):
|
||||
st = os.statvfs(self._path)
|
||||
self._size = st.f_blocks * st.f_frsize
|
||||
self._used = (st.f_blocks - st.f_bfree) * st.f_frsize
|
||||
self._left = self._size - self._used;
|
||||
self._percent = 100.0 * self._used/self._size
|
||||
|
||||
def state(self, widget):
|
||||
return self.threshold_state(self._percent, 80, 90)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,77 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays DNF package update information (<security>/<bugfixes>/<enhancements>/<other>)
|
||||
|
||||
Requires the following executable:
|
||||
* dnf
|
||||
|
||||
Parameters:
|
||||
* dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes)
|
||||
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
def get_dnf_info(widget):
|
||||
try:
|
||||
res = bumblebee.util.execute("dnf updateinfo")
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
security = 0
|
||||
bugfixes = 0
|
||||
enhancements = 0
|
||||
other = 0
|
||||
for line in res.split("\n"):
|
||||
|
||||
if not line.startswith(" "): continue
|
||||
elif "ecurity" in line:
|
||||
for s in line.split():
|
||||
if s.isdigit(): security += int(s)
|
||||
elif "ugfix" in line:
|
||||
for s in line.split():
|
||||
if s.isdigit(): bugfixes += int(s)
|
||||
elif "hancement" in line:
|
||||
for s in line.split():
|
||||
if s.isdigit(): enhancements += int(s)
|
||||
else:
|
||||
for s in line.split():
|
||||
if s.isdigit(): other += int(s)
|
||||
|
||||
widget.set("security", security)
|
||||
widget.set("bugfixes", bugfixes)
|
||||
widget.set("enhancements", enhancements)
|
||||
widget.set("other", other)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.updates)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
self.interval_factor(60)
|
||||
self.interval(30)
|
||||
|
||||
def updates(self, widget):
|
||||
result = []
|
||||
for t in ["security", "bugfixes", "enhancements", "other"]:
|
||||
result.append(str(widget.get(t, 0)))
|
||||
return "/".join(result)
|
||||
|
||||
def update(self, widgets):
|
||||
thread = threading.Thread(target=get_dnf_info, args=(widgets[0],))
|
||||
thread.start()
|
||||
|
||||
def state(self, widget):
|
||||
cnt = 0
|
||||
for t in ["security", "bugfixes", "enhancements", "other"]:
|
||||
cnt += widget.get(t, 0)
|
||||
if cnt == 0:
|
||||
return "good"
|
||||
if cnt > 50 or widget.get("security", 0) > 0:
|
||||
return "critical"
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,50 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Displays the number of docker containers running
|
||||
|
||||
Requires the following python packages:
|
||||
* docker
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
import docker
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from requests.exceptions import ConnectionError
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = bumblebee.output.Widget(full_text=self.status)
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
self._status = self.status
|
||||
self._state = self.state
|
||||
|
||||
def update(self, widgets):
|
||||
self._status = self.status
|
||||
self._state = self.state
|
||||
|
||||
def state(self, widget):
|
||||
state = []
|
||||
status = self.status(widget)
|
||||
if status == "OK - 0":
|
||||
state.append("warning")
|
||||
elif status in ["n/a", "Daemon off"]:
|
||||
state.append("critical")
|
||||
return state
|
||||
|
||||
def status(self, widget):
|
||||
try:
|
||||
cli = docker.DockerClient(base_url='unix://var/run/docker.sock')
|
||||
cli.ping()
|
||||
except ConnectionError:
|
||||
return "Daemon off"
|
||||
except Exception:
|
||||
return "n/a"
|
||||
return "OK - {}".format(len(cli.containers.list(filters={'status': "running"})))
|
|
@ -1,39 +0,0 @@
|
|||
#pylint: disable=C0111,R0903
|
||||
|
||||
"""Toggle dunst notifications."""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text="")
|
||||
)
|
||||
self._paused = False
|
||||
# Make sure that dunst is currently not paused
|
||||
try:
|
||||
bumblebee.util.execute("killall -SIGUSR2 dunst")
|
||||
except:
|
||||
pass
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.toggle_status
|
||||
)
|
||||
|
||||
def toggle_status(self, event):
|
||||
self._paused = not self._paused
|
||||
|
||||
try:
|
||||
if self._paused:
|
||||
bumblebee.util.execute("killall -SIGUSR1 dunst")
|
||||
else:
|
||||
bumblebee.util.execute("killall -SIGUSR2 dunst")
|
||||
except:
|
||||
self._paused = not self._paused # toggling failed
|
||||
|
||||
def state(self, widget):
|
||||
if self._paused:
|
||||
return ["muted", "warning"]
|
||||
return ["unmuted"]
|
|
@ -1,26 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Draws an error widget.
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.text)
|
||||
)
|
||||
self._text = ""
|
||||
|
||||
def set(self, text):
|
||||
self._text = text
|
||||
|
||||
def text(self, widget):
|
||||
return self._text
|
||||
|
||||
def state(self, widget):
|
||||
return ["critical"]
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,75 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the price of a cryptocurrency.
|
||||
|
||||
Requires the following python packages:
|
||||
* requests
|
||||
|
||||
Parameters:
|
||||
* getcrypto.interval: Interval in seconds for updating the price, default is 120, less than that will probably get your IP banned.
|
||||
* getcrypto.getbtc: 0 for not getting price of BTC, 1 for getting it (default).
|
||||
* getcrypto.geteth: 0 for not getting price of ETH, 1 for getting it (default).
|
||||
* getcrypto.getltc: 0 for not getting price of LTC, 1 for getting it (default).
|
||||
* getcrypto.getcur: Set the currency to display the price in, usd is the default.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import time
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
from requests.exceptions import RequestException
|
||||
def getfromkrak(coin, currency):
|
||||
abbrev = {
|
||||
"Btc": ["xbt", "XXBTZ"],
|
||||
"Eth": ["eth", "XETHZ"],
|
||||
"Ltc": ["ltc", "XLTCZ"],
|
||||
}
|
||||
data = abbrev.get(coin, None)
|
||||
if not data: return
|
||||
epair = "{}{}".format(data[0], currency)
|
||||
tickname = "{}{}".format(data[1], currency.upper())
|
||||
try:
|
||||
krakenget = requests.get('https://api.kraken.com/0/public/Ticker?pair='+epair).json()
|
||||
except (RequestException, Exception):
|
||||
return "No connection"
|
||||
if not 'result' in krakenget:
|
||||
return "No data"
|
||||
kethusdask = float(krakenget['result'][tickname]['a'][0])
|
||||
kethusdbid = float(krakenget['result'][tickname]['b'][0])
|
||||
return coin+": "+str((kethusdask+kethusdbid)/2)[0:6]
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.curprice)
|
||||
)
|
||||
self._curprice = ""
|
||||
self._nextcheck = 0
|
||||
self._interval = int(self.parameter("interval", "120"))
|
||||
self._getbtc = bumblebee.util.asbool(self.parameter("getbtc", True))
|
||||
self._geteth = bumblebee.util.asbool(self.parameter("geteth", True))
|
||||
self._getltc = bumblebee.util.asbool(self.parameter("getltc", True))
|
||||
self._getcur = self.parameter("getcur", "usd")
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="xdg-open https://cryptowat.ch/")
|
||||
|
||||
def curprice(self, widget):
|
||||
return self._curprice
|
||||
|
||||
def update(self, widgets):
|
||||
if self._nextcheck < int(time.time()):
|
||||
self._nextcheck = int(time.time()) + self._interval
|
||||
currency = self._getcur
|
||||
btcprice, ethprice, ltcprice = "", "", ""
|
||||
if self._getbtc:
|
||||
btcprice = getfromkrak('Btc', currency)
|
||||
if self._geteth:
|
||||
ethprice = getfromkrak('Eth', currency)
|
||||
if self._getltc:
|
||||
ltcprice = getfromkrak('Ltc', currency)
|
||||
self._curprice = btcprice+" "*(self._getbtc*self._geteth)+ethprice+" "*(self._getltc*max(self._getbtc, self._geteth))+ltcprice
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,81 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Print the branch and git status for the
|
||||
currently focused window.
|
||||
|
||||
Requires:
|
||||
* xcwd
|
||||
* Python module 'pygit2'
|
||||
"""
|
||||
|
||||
import os
|
||||
import string
|
||||
import logging
|
||||
try:
|
||||
import pygit2
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = []
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
self._engine = engine
|
||||
self._error = False
|
||||
self.update(self.widgets())
|
||||
|
||||
def hidden(self):
|
||||
return self._error
|
||||
|
||||
def update(self, widgets):
|
||||
state = {}
|
||||
new_widgets = []
|
||||
try:
|
||||
directory = bumblebee.util.execute("xcwd").strip()
|
||||
directory = self._get_git_root(directory)
|
||||
repo = pygit2.Repository(directory)
|
||||
|
||||
new_widgets.append(bumblebee.output.Widget(name='git.main', full_text=repo.head.shorthand))
|
||||
|
||||
for filepath, flags in repo.status().items():
|
||||
if flags == pygit2.GIT_STATUS_WT_NEW or \
|
||||
flags == pygit2.GIT_STATUS_INDEX_NEW:
|
||||
state['new'] = True
|
||||
if flags == pygit2.GIT_STATUS_WT_DELETED or \
|
||||
flags == pygit2.GIT_STATUS_INDEX_DELETED:
|
||||
state['deleted'] = True
|
||||
if flags == pygit2.GIT_STATUS_WT_MODIFIED or \
|
||||
flags == pygit2.GIT_STATUS_INDEX_MODIFIED:
|
||||
state['modified'] = True
|
||||
self._error = False
|
||||
if 'new' in state:
|
||||
new_widgets.append(bumblebee.output.Widget(name='git.new'))
|
||||
if 'modified' in state:
|
||||
new_widgets.append(bumblebee.output.Widget(name='git.modified'))
|
||||
if 'deleted' in state:
|
||||
new_widgets.append(bumblebee.output.Widget(name='git.deleted'))
|
||||
|
||||
while len(widgets) > 0:
|
||||
del widgets[0]
|
||||
for widget in new_widgets:
|
||||
widgets.append(widget)
|
||||
|
||||
except Exception as e:
|
||||
self._error = True
|
||||
|
||||
def state(self, widget):
|
||||
return widget.name.split('.')[1]
|
||||
|
||||
def _get_git_root(self, directory):
|
||||
while len(directory) > 1:
|
||||
if os.path.exists(os.path.join(directory, ".git")):
|
||||
return directory
|
||||
directory = "/".join(directory.split("/")[0:-1])
|
||||
return "/"
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,62 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the unread GitHub notifications for a GitHub user
|
||||
|
||||
Requires the following library:
|
||||
* requests
|
||||
|
||||
Parameters:
|
||||
* github.token: GitHub user access token, the token needs to have the 'notifications' scope.
|
||||
* github.interval: Interval in minutes between updates, default is 5.
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
import bumblebee.util
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.github)
|
||||
)
|
||||
self._count = 0
|
||||
self.interval_factor(60)
|
||||
self.interval(5)
|
||||
self._requests = requests.Session()
|
||||
|
||||
cmd = "xdg-open"
|
||||
if not bumblebee.util.which(cmd):
|
||||
cmd = "x-www-browser"
|
||||
self._requests.headers.update({"Authorization":"token {}".format(self.parameter("token", ""))})
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="{} https://github.com/notifications".format(cmd))
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update)
|
||||
|
||||
def github(self, _):
|
||||
return str(self._count)
|
||||
|
||||
def update(self, _):
|
||||
try:
|
||||
self._count = 0
|
||||
url = "https://api.github.com/notifications"
|
||||
while True:
|
||||
notifications = self._requests.get(url)
|
||||
self._count += len(list(filter(lambda notification: notification['unread'], notifications.json())))
|
||||
next_link = notifications.links.get('next')
|
||||
if next_link is not None:
|
||||
url = next_link.get('url')
|
||||
else:
|
||||
break
|
||||
|
||||
except Exception:
|
||||
self._count = "n/a"
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,56 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays information about the current song in Google Play music player.
|
||||
|
||||
Requires the following executable:
|
||||
* gpmdp-remote
|
||||
"""
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = [
|
||||
bumblebee.output.Widget(name="gpmdp.prev"),
|
||||
bumblebee.output.Widget(name="gpmdp.main", full_text=self.description),
|
||||
bumblebee.output.Widget(name="gpmdp.next"),
|
||||
]
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
|
||||
engine.input.register_callback(widgets[0], button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="playerctl previous")
|
||||
engine.input.register_callback(widgets[1], button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="playerctl play-pause")
|
||||
engine.input.register_callback(widgets[2], button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="playerctl next")
|
||||
|
||||
self._status = None
|
||||
self._tags = None
|
||||
|
||||
def description(self, widget):
|
||||
return self._tags if self._tags else "n/a"
|
||||
|
||||
def update(self, widgets):
|
||||
self._load_song()
|
||||
|
||||
def state(self, widget):
|
||||
if widget.name == "gpmdp.prev":
|
||||
return "prev"
|
||||
if widget.name == "gpmdp.next":
|
||||
return "next"
|
||||
return self._status
|
||||
|
||||
def _load_song(self):
|
||||
info = ""
|
||||
try:
|
||||
info = bumblebee.util.execute("gpmdp-remote current")
|
||||
status = bumblebee.util.execute("gpmdp-remote status")
|
||||
except RuntimeError:
|
||||
pass
|
||||
self._status = status.split("\n")[0].lower()
|
||||
self._tags = info.split("\n")[0]
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,24 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the system hostname."""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.output)
|
||||
)
|
||||
self._hname = ""
|
||||
|
||||
def output(self, _):
|
||||
return self._hname+" "+u"\uf233"
|
||||
|
||||
def update(self, widgets):
|
||||
with open('/proc/sys/kernel/hostname', 'r') as f:
|
||||
self._hname = f.readline().split()[0]
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,68 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Display HTTP status code
|
||||
|
||||
Parameters:
|
||||
* http_status.label: Prefix label (optional)
|
||||
* http_status.target: Target to retrieve the HTTP status from
|
||||
* http_status.expect: Expected HTTP status
|
||||
"""
|
||||
|
||||
from requests import head
|
||||
|
||||
import psutil
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
UNK = "UNK"
|
||||
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.output)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
|
||||
self._label = self.parameter("label")
|
||||
self._target = self.parameter("target")
|
||||
self._expect = self.parameter("expect", "200")
|
||||
self._status = self.getStatus()
|
||||
self._output = self.getOutput()
|
||||
|
||||
def labelize(self, s):
|
||||
if self._label is None:
|
||||
return s
|
||||
return "{}: {}".format(self._label, s)
|
||||
|
||||
def getStatus(self):
|
||||
try:
|
||||
res = head(self._target)
|
||||
except Exception:
|
||||
return self.UNK
|
||||
else:
|
||||
status = str(res.status_code)
|
||||
self._status = status
|
||||
return status
|
||||
|
||||
def getOutput(self):
|
||||
if self._status == self._expect:
|
||||
return self.labelize(self._status)
|
||||
else:
|
||||
reason = " != {}".format(self._expect)
|
||||
return self.labelize("{}{}".format(self._status, reason))
|
||||
|
||||
def output(self, widget):
|
||||
return self._output
|
||||
|
||||
def update(self, widgets):
|
||||
self.getStatus()
|
||||
self._output = self.getOutput()
|
||||
|
||||
def state(self, widget):
|
||||
if self._status == self.UNK:
|
||||
return "warning"
|
||||
if self._status != self._expect:
|
||||
return "critical"
|
||||
return self._output
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,51 +0,0 @@
|
|||
#pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the indicator status, for numlock, scrolllock and capslock
|
||||
|
||||
Parameters:
|
||||
* indicator.include: Comma-separated list of interface prefixes to include (defaults to "numlock,capslock")
|
||||
* indicator.signalstype: If you want the signali type color to be "critical" or "warning" (defaults to "warning")
|
||||
"""
|
||||
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = []
|
||||
self.status = False
|
||||
super(Module,self).__init__(engine, config, widgets)
|
||||
self._include = tuple(filter(len, self.parameter("include", "NumLock,CapsLock").split(",")))
|
||||
self._signalType = self.parameter("signaltype") if not self.parameter("signaltype") is None else "warning"
|
||||
|
||||
def update(self, widgets):
|
||||
self._update_widgets(widgets)
|
||||
|
||||
def state(self, widget):
|
||||
states = []
|
||||
if widget.status:
|
||||
states.append(self._signalType)
|
||||
elif not widget.status:
|
||||
states.append("normal")
|
||||
return states
|
||||
|
||||
def _update_widgets(self, widgets):
|
||||
status_line = ""
|
||||
for line in bumblebee.util.execute("xset q").replace(" ", "").split("\n"):
|
||||
if "capslock" in line.lower():
|
||||
status_line = line
|
||||
break
|
||||
|
||||
for indicator in self._include:
|
||||
widget = self.widget(indicator)
|
||||
if not widget:
|
||||
widget = bumblebee.output.Widget(name=indicator)
|
||||
widgets.append(widget)
|
||||
|
||||
widget.status = True if indicator.lower()+":on" in status_line.lower() else False
|
||||
widget.full_text(indicator)
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,21 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Shows Linux kernel version information"""
|
||||
|
||||
import platform
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.output)
|
||||
)
|
||||
self._release_name = platform.release()
|
||||
|
||||
def output(self, widget):
|
||||
return self._release_name
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,65 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current keyboard layout using libX11
|
||||
|
||||
Requires the following library:
|
||||
* libX11.so.6
|
||||
and python module:
|
||||
* xkbgroup
|
||||
|
||||
Parameters:
|
||||
* layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed)
|
||||
* layout-xkb.show_variant: Boolean that indecates whether the variant name should be displayed. Defaults to true.
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
has_xkb = True
|
||||
try:
|
||||
from xkbgroup import *
|
||||
except ImportError:
|
||||
has_xkb = False
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.current_layout)
|
||||
)
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self._next_keymap)
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
||||
cmd=self._prev_keymap)
|
||||
self._show_variant = bumblebee.util.asbool(self.parameter("show_variant", "true"))
|
||||
|
||||
def _next_keymap(self, event):
|
||||
self._set_keymap(1)
|
||||
|
||||
def _prev_keymap(self, event):
|
||||
self._set_keymap(-1)
|
||||
|
||||
def _set_keymap(self, rotation):
|
||||
if not has_xkb: return
|
||||
|
||||
xkb = XKeyboard()
|
||||
if xkb.groups_count < 2: return # nothing to doA
|
||||
layouts = xkb.groups_symbols
|
||||
idx = layouts.index(xkb.group_symbol)
|
||||
xkb.group_symbol = str(layouts[(idx + rotation) % len(layouts)])
|
||||
|
||||
def current_layout(self, widget):
|
||||
try:
|
||||
xkb = XKeyboard()
|
||||
log.debug("group num: {}".format(xkb.group_num))
|
||||
name = xkb.group_name if bumblebee.util.asbool(self.parameter("showname")) else xkb.group_symbol
|
||||
if self._show_variant:
|
||||
return "{} ({})".format(name, xkb.group_variant) if xkb.group_variant else name
|
||||
return name
|
||||
except Exception:
|
||||
return "n/a"
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,40 +0,0 @@
|
|||
"""Displays and changes the current keyboard layout
|
||||
|
||||
Requires the following executable:
|
||||
* xkb-switch
|
||||
"""
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.current_layout)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
engine.input.register_callback(
|
||||
self,
|
||||
button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self._next_keymap)
|
||||
self._current_layout = self._get_current_layout()
|
||||
|
||||
def current_layout(self, __):
|
||||
return self._current_layout
|
||||
|
||||
def _next_keymap(self, event):
|
||||
try:
|
||||
bumblebee.util.execute("xkb-switch -n")
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def _get_current_layout(self):
|
||||
try:
|
||||
res = bumblebee.util.execute("xkb-switch")
|
||||
return res.split("\n")[0]
|
||||
except RuntimeError:
|
||||
return ["n/a"]
|
||||
|
||||
def update(self, __):
|
||||
self._current_layout = self._get_current_layout()
|
|
@ -1,30 +0,0 @@
|
|||
"""Displays count of running libvirt VMs.
|
||||
Required the following python packages:
|
||||
* libvirt
|
||||
* sys
|
||||
"""
|
||||
import sys
|
||||
import libvirt
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.status)
|
||||
)
|
||||
self._status = self.status
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="virt-manager")
|
||||
|
||||
def update(self, widgets):
|
||||
self._status = self.status
|
||||
|
||||
def status(self, _):
|
||||
conn = None
|
||||
conn = libvirt.openReadOnly(None)
|
||||
if conn == None:
|
||||
print ('Failed to open connection to the hypervisor')
|
||||
return "VMs %s" % (conn.numOfDomains())
|
|
@ -1,72 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays available RAM, total amount of RAM and percentage available.
|
||||
|
||||
Parameters:
|
||||
* memory.warning : Warning threshold in % of memory used (defaults to 80%)
|
||||
* memory.critical: Critical threshold in % of memory used (defaults to 90%)
|
||||
* memory.format: Format string (defaults to "{used}/{total} ({percent:05.02f}%)")
|
||||
* memory.usedonly: Only show the amount of RAM in use (defaults to False). Same as memory.format="{used}"
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Container(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.memory_usage)
|
||||
)
|
||||
self.update(None)
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="gnome-system-monitor")
|
||||
|
||||
@property
|
||||
def _format(self):
|
||||
if bumblebee.util.asbool(self.parameter("usedonly", False)):
|
||||
return "{used}"
|
||||
else:
|
||||
return self.parameter("format", "{used}/{total} ({percent:05.02f}%)")
|
||||
|
||||
def memory_usage(self, widget):
|
||||
return self._format.format(**self._mem)
|
||||
|
||||
def update(self, widgets):
|
||||
data = {}
|
||||
with open("/proc/meminfo", "r") as f:
|
||||
for line in f:
|
||||
tmp = re.split(r"[:\s]+", line)
|
||||
value = int(tmp[1])
|
||||
if tmp[2] == "kB": value = value*1024
|
||||
if tmp[2] == "mB": value = value*1024*1024
|
||||
if tmp[2] == "gB": value = value*1024*1024*1024
|
||||
data[tmp[0]] = value
|
||||
if "MemAvailable" in data:
|
||||
used = data["MemTotal"] - data["MemAvailable"]
|
||||
else:
|
||||
used = data["MemTotal"] - data["MemFree"] - data["Buffers"] - data["Cached"] - data["Slab"]
|
||||
self._mem = {
|
||||
"total": bumblebee.util.bytefmt(data["MemTotal"]),
|
||||
"available": bumblebee.util.bytefmt(data["MemAvailable"]),
|
||||
"free": bumblebee.util.bytefmt(data["MemFree"]),
|
||||
"used": bumblebee.util.bytefmt(used),
|
||||
"percent": float(used)/float(data["MemTotal"])*100.0
|
||||
}
|
||||
|
||||
def state(self, widget):
|
||||
if self._mem["percent"] > float(self.parameter("critical", 90)):
|
||||
return "critical"
|
||||
if self._mem["percent"] > float(self.parameter("warning", 80)):
|
||||
return "warning"
|
||||
return None
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,60 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Displays information about the current song in mocp. Left click toggles play/pause. Right click toggles shuffle.
|
||||
|
||||
Requires the following executable:
|
||||
* mocp
|
||||
|
||||
Parameters:
|
||||
* mocp.format: Format string for the song information. Replace string sequences with the actual information:
|
||||
%state State
|
||||
%file File
|
||||
%title Title, includes track, artist, song title and album
|
||||
%artist Artist
|
||||
%song SongTitle
|
||||
%album Album
|
||||
%tt TotalTime
|
||||
%tl TimeLeft
|
||||
%ts TotalSec
|
||||
%ct CurrentTime
|
||||
%cs CurrentSec
|
||||
%b Bitrate
|
||||
%r Sample rate
|
||||
"""
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from bumblebee.output import scrollable
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(name="mocp.main", full_text=self.description)
|
||||
)
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="mocp -G")
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
||||
cmd="mocp -t shuffle")
|
||||
self._format = self.parameter("format", "%state %artist - %song | %ct/%tt")
|
||||
self._running = 0
|
||||
|
||||
#@scrollable
|
||||
def description(self, widget):
|
||||
return self._info if self._running == 1 else "Music On Console Player"
|
||||
|
||||
def update(self, widgets):
|
||||
self._load_song()
|
||||
|
||||
def _load_song(self):
|
||||
try:
|
||||
self._info = bumblebee.util.execute("mocp -Q '" + self._format + "'" ).strip()
|
||||
self._running = 1
|
||||
except RuntimeError:
|
||||
self._running = 0
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,181 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Displays information about the current song in mpd.
|
||||
|
||||
Requires the following executable:
|
||||
* mpc
|
||||
|
||||
Parameters:
|
||||
* mpd.format: Format string for the song information.
|
||||
Supported tags (see `man mpc` for additional information)
|
||||
* {name}
|
||||
* {artist}
|
||||
* {album}
|
||||
* {albumartist}
|
||||
* {comment}
|
||||
* {composer}
|
||||
* {date}
|
||||
* {originaldate}
|
||||
* {disc}
|
||||
* {genre}
|
||||
* {performer}
|
||||
* {title}
|
||||
* {track}
|
||||
* {time}
|
||||
* {file}
|
||||
* {id}
|
||||
* {prio}
|
||||
* {mtime}
|
||||
* {mdate}
|
||||
Additional tags:
|
||||
* {position} - position of currently playing song
|
||||
not to be confused with %position% mpc tag
|
||||
* {duration} - duration of currently playing song
|
||||
* {file1} - song file name without path prefix
|
||||
if {file} = '/foo/bar.baz', then {file1} = 'bar.baz'
|
||||
* {file2} - song file name without path prefix and extension suffix
|
||||
if {file} = '/foo/bar.baz', then {file2} = 'bar'
|
||||
* mpd.host: MPD host to connect to. (mpc behaviour by default)
|
||||
* mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import string
|
||||
import os
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from bumblebee.output import scrollable
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config, [])
|
||||
|
||||
self._layout = self.parameter("layout", "mpd.prev mpd.main mpd.next mpd.shuffle mpd.repeat")
|
||||
|
||||
self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}")
|
||||
self._status = None
|
||||
self._shuffle = False
|
||||
self._repeat = False
|
||||
self._tags = defaultdict(lambda: '')
|
||||
|
||||
if not self.parameter("host"):
|
||||
self._hostcmd = ""
|
||||
else:
|
||||
self._hostcmd = " -h " + self.parameter("host")
|
||||
|
||||
# Create widgets
|
||||
widget_list = []
|
||||
widget_map = {}
|
||||
for widget_name in self._layout.split():
|
||||
widget = bumblebee.output.Widget(name=widget_name)
|
||||
widget_list.append(widget)
|
||||
|
||||
if widget_name == "mpd.prev":
|
||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc prev" + self._hostcmd}
|
||||
elif widget_name == "mpd.main":
|
||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc toggle" + self._hostcmd}
|
||||
widget.full_text(self.description)
|
||||
elif widget_name == "mpd.next":
|
||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc next" + self._hostcmd}
|
||||
elif widget_name == "mpd.shuffle":
|
||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc random" + self._hostcmd}
|
||||
elif widget_name == "mpd.repeat":
|
||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc repeat" + self._hostcmd}
|
||||
else:
|
||||
raise KeyError("The mpd module does not support a {widget_name!r} widget".format(widget_name=widget_name))
|
||||
self.widgets(widget_list)
|
||||
|
||||
# Register input callbacks
|
||||
for widget, callback_options in widget_map.items():
|
||||
engine.input.register_callback(widget, **callback_options)
|
||||
|
||||
def hidden(self):
|
||||
return self._status is None
|
||||
|
||||
@scrollable
|
||||
def description(self, widget):
|
||||
return string.Formatter().vformat(self._fmt, (), self._tags)
|
||||
|
||||
def update(self, widgets):
|
||||
self._load_song()
|
||||
|
||||
def state(self, widget):
|
||||
if widget.name == "mpd.shuffle":
|
||||
return "shuffle-on" if self._shuffle else "shuffle-off"
|
||||
if widget.name == "mpd.repeat":
|
||||
return "repeat-on" if self._repeat else "repeat-off"
|
||||
if widget.name == "mpd.prev":
|
||||
return "prev"
|
||||
if widget.name == "mpd.next":
|
||||
return "next"
|
||||
return self._status
|
||||
|
||||
def _load_song(self):
|
||||
info = ""
|
||||
try:
|
||||
tags = ['name',
|
||||
'artist',
|
||||
'album',
|
||||
'albumartist',
|
||||
'comment',
|
||||
'composer',
|
||||
'date',
|
||||
'originaldate',
|
||||
'disc',
|
||||
'genre',
|
||||
'performer',
|
||||
'title',
|
||||
'track',
|
||||
'time',
|
||||
'file',
|
||||
'id',
|
||||
'prio',
|
||||
'mtime',
|
||||
'mdate']
|
||||
joinedtags = "\n".join(["tag {0} %{0}%".format(tag) for tag in tags])
|
||||
info = bumblebee.util.execute('mpc -f ' + '"' + joinedtags + '"' + self._hostcmd)
|
||||
except RuntimeError:
|
||||
pass
|
||||
self._tags = defaultdict(lambda: '')
|
||||
self._status = None
|
||||
for line in info.split("\n"):
|
||||
if line.startswith("[playing]"):
|
||||
self._status = "playing"
|
||||
elif line.startswith("[paused]"):
|
||||
self._status = "paused"
|
||||
|
||||
if line.startswith("["):
|
||||
timer = line.split()[2]
|
||||
position = timer.split("/")[0]
|
||||
dur = timer.split("/")[1]
|
||||
duration = dur.split(" ")[0]
|
||||
self._tags.update({'position': position})
|
||||
self._tags.update({'duration': duration})
|
||||
|
||||
if line.startswith("volume"):
|
||||
value = line.split(" ", 2)[1:]
|
||||
for option in value:
|
||||
if option.startswith("repeat: on"):
|
||||
self._repeat = True
|
||||
elif option.startswith("repeat: off"):
|
||||
self._repeat = False
|
||||
elif option.startswith("random: on"):
|
||||
self._shuffle = True
|
||||
elif option.startswith("random: off"):
|
||||
self._shuffle = False
|
||||
if line.startswith("tag"):
|
||||
key, value = line.split(" ", 2)[1:]
|
||||
self._tags.update({key: value})
|
||||
if key == "file":
|
||||
self._tags.update({"file1": os.path.basename(value)})
|
||||
self._tags.update(
|
||||
{"file2":
|
||||
os.path.splitext(os.path.basename(value))[0]})
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,114 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Displays network traffic
|
||||
* No extra configuration needed
|
||||
"""
|
||||
|
||||
import psutil
|
||||
import netifaces
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
WIDGET_NAME = 'network_traffic'
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
"""Bumblebee main module """
|
||||
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config)
|
||||
|
||||
try:
|
||||
self._bandwidth = BandwidthInfo()
|
||||
|
||||
self._bytes_recv = self._bandwidth.bytes_recv()
|
||||
self._bytes_sent = self._bandwidth.bytes_sent()
|
||||
except Exception:
|
||||
""" We do not want do explode anything """
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def state(cls, widget):
|
||||
"""Return the widget state"""
|
||||
|
||||
if widget.name == '{}.rx'.format(WIDGET_NAME):
|
||||
return 'rx'
|
||||
elif widget.name == '{}.tx'.format(WIDGET_NAME):
|
||||
return 'tx'
|
||||
|
||||
return None
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
bytes_recv = self._bandwidth.bytes_recv()
|
||||
bytes_sent = self._bandwidth.bytes_sent()
|
||||
|
||||
download_rate = (bytes_recv - self._bytes_recv)
|
||||
upload_rate = (bytes_sent - self._bytes_sent)
|
||||
|
||||
self.update_widgets(widgets, download_rate, upload_rate)
|
||||
|
||||
self._bytes_recv, self._bytes_sent = bytes_recv, bytes_sent
|
||||
except Exception:
|
||||
""" We do not want do explode anything """
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def update_widgets(cls, widgets, download_rate, upload_rate):
|
||||
"""Update tx/rx widgets with new rates"""
|
||||
del widgets[:]
|
||||
|
||||
widgets.extend((
|
||||
TrafficWidget(text=download_rate, direction='rx'),
|
||||
TrafficWidget(text=upload_rate, direction='tx')
|
||||
))
|
||||
|
||||
|
||||
class BandwidthInfo(object):
|
||||
"""Get received/sent bytes from network adapter"""
|
||||
|
||||
def bytes_recv(self):
|
||||
"""Return received bytes"""
|
||||
return self.bandwidth().bytes_recv
|
||||
|
||||
def bytes_sent(self):
|
||||
"""Return sent bytes"""
|
||||
return self.bandwidth().bytes_sent
|
||||
|
||||
def bandwidth(self):
|
||||
"""Return bandwidth information"""
|
||||
io_counters = self.io_counters()
|
||||
return io_counters[self.default_network_adapter()]
|
||||
|
||||
@classmethod
|
||||
def default_network_adapter(cls):
|
||||
"""Return default active network adapter"""
|
||||
gateway = netifaces.gateways()['default']
|
||||
|
||||
if not gateway:
|
||||
raise 'No default gateway found'
|
||||
|
||||
return gateway[netifaces.AF_INET][1]
|
||||
|
||||
@classmethod
|
||||
def io_counters(cls):
|
||||
"""Return IO counters"""
|
||||
return psutil.net_io_counters(pernic=True)
|
||||
|
||||
class TrafficWidget(object):
|
||||
"""Create a traffic widget with humanized bytes string with proper icon (up/down)"""
|
||||
def __new__(cls, text, direction):
|
||||
widget = bumblebee.output.Widget(name='{0}.{1}'.format(WIDGET_NAME, direction))
|
||||
widget.set('theme.minwidth', '0000000KiB/s')
|
||||
widget.full_text(cls.humanize(text))
|
||||
|
||||
return widget
|
||||
|
||||
@staticmethod
|
||||
def humanize(text):
|
||||
"""Return humanized bytes"""
|
||||
humanized_byte_format = bumblebee.util.bytefmt(text)
|
||||
return '{0}/s'.format(humanized_byte_format)
|
|
@ -1,119 +0,0 @@
|
|||
#pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the name, IP address(es) and status of each available network interface.
|
||||
|
||||
Requires the following python module:
|
||||
* netifaces
|
||||
|
||||
Parameters:
|
||||
* nic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth,br")
|
||||
* nic.include: Comma-separated list of interfaces to include
|
||||
* nic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -> show all devices that are not in state down)
|
||||
* nic.format: Format string (defaults to "{intf} {state} {ip} {ssid}")
|
||||
"""
|
||||
|
||||
import netifaces
|
||||
import subprocess
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = []
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth,br").split(",")))
|
||||
self._include = self.parameter("include", "").split(",")
|
||||
|
||||
self._states = {}
|
||||
self._states["include"] = []
|
||||
self._states["exclude"] = []
|
||||
for state in tuple(filter(len, self.parameter("states", "").split(","))):
|
||||
if state[0] == "^":
|
||||
self._states["exclude"].append(state[1:])
|
||||
else:
|
||||
self._states["include"].append(state)
|
||||
self._format = self.parameter("format","{intf} {state} {ip} {ssid}");
|
||||
self._update_widgets(widgets)
|
||||
self.iwgetid = bumblebee.util.which("iwgetid")
|
||||
|
||||
|
||||
def update(self, widgets):
|
||||
self._update_widgets(widgets)
|
||||
|
||||
def state(self, widget):
|
||||
states = []
|
||||
|
||||
if widget.get("state") == "down":
|
||||
states.append("critical")
|
||||
elif widget.get("state") != "up":
|
||||
states.append("warning")
|
||||
|
||||
intf = widget.get("intf")
|
||||
iftype = "wireless" if self._iswlan(intf) else "wired"
|
||||
iftype = "tunnel" if self._istunnel(intf) else iftype
|
||||
|
||||
states.append("{}-{}".format(iftype, widget.get("state")))
|
||||
|
||||
return states
|
||||
|
||||
def _iswlan(self, intf):
|
||||
# wifi, wlan, wlp, seems to work for me
|
||||
if intf.startswith("w"): return True
|
||||
return False
|
||||
|
||||
def _istunnel(self, intf):
|
||||
return intf.startswith("tun") or intf.startswith("wg")
|
||||
|
||||
def get_addresses(self, intf):
|
||||
retval = []
|
||||
try:
|
||||
for ip in netifaces.ifaddresses(intf).get(netifaces.AF_INET, []):
|
||||
if ip.get("addr", "") != "":
|
||||
retval.append(ip.get("addr"))
|
||||
except Exception:
|
||||
return []
|
||||
return retval
|
||||
|
||||
def _update_widgets(self, widgets):
|
||||
interfaces = [i for i in netifaces.interfaces() if not i.startswith(self._exclude)]
|
||||
interfaces.extend([i for i in netifaces.interfaces() if i in self._include])
|
||||
|
||||
for widget in widgets:
|
||||
widget.set("visited", False)
|
||||
|
||||
for intf in interfaces:
|
||||
addr = []
|
||||
state = "down"
|
||||
for ip in self.get_addresses(intf):
|
||||
addr.append(ip)
|
||||
state = "up"
|
||||
|
||||
if len(self._states["exclude"]) > 0 and state in self._states["exclude"]: continue
|
||||
if len(self._states["include"]) > 0 and state not in self._states["include"]: continue
|
||||
|
||||
widget = self.widget(intf)
|
||||
if not widget:
|
||||
widget = bumblebee.output.Widget(name=intf)
|
||||
widgets.append(widget)
|
||||
# join/split is used to get rid of multiple whitespaces (in case SSID is not available, for instance
|
||||
widget.full_text(" ".join(self._format.format(ip=", ".join(addr),intf=intf,state=state,ssid=self.get_ssid(intf)).split()))
|
||||
widget.set("intf", intf)
|
||||
widget.set("state", state)
|
||||
widget.set("visited", True)
|
||||
|
||||
for widget in widgets:
|
||||
if widget.get("visited") is False:
|
||||
widgets.remove(widget)
|
||||
|
||||
def get_ssid(self, intf):
|
||||
if self._iswlan(intf):
|
||||
try:
|
||||
return subprocess.check_output([self.iwgetid,"-r",intf]).strip().decode('utf-8')
|
||||
except:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,51 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the result of a notmuch count query
|
||||
default : unread emails which path do not contained "Trash" (notmuch count "tag:unread AND NOT path:/.*Trash.*/")
|
||||
|
||||
Parameters:
|
||||
* notmuch_count.query: notmuch count query to show result
|
||||
|
||||
Errors:
|
||||
if the notmuch query failed, the shown value is -1
|
||||
|
||||
Dependencies:
|
||||
notmuch (https://notmuchmail.org/)
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import os
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
|
||||
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.output)
|
||||
)
|
||||
self._notmuch_count_query = self.parameter("query", "tag:unread AND NOT path:/.*Trash.*/")
|
||||
self._notmuch_count = self.count_notmuch()
|
||||
|
||||
|
||||
def output(self, widget):
|
||||
self._notmuch_count = self.count_notmuch()
|
||||
return str(self._notmuch_count)
|
||||
|
||||
|
||||
def state(self, widgets):
|
||||
if self._notmuch_count == 0:
|
||||
return "empty"
|
||||
return "items"
|
||||
|
||||
|
||||
def count_notmuch(self):
|
||||
try:
|
||||
notmuch_count_cmd = "notmuch count " + self._notmuch_count_query
|
||||
notmuch_count = int(bumblebee.util.execute(notmuch_count_cmd))
|
||||
return notmuch_count
|
||||
except Exception:
|
||||
return -1
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,79 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Displays GPU name, temperature and memory usage.
|
||||
|
||||
Parameters:
|
||||
* nvidiagpu.format: Format string (defaults to "{name}: {temp}°C %{usedmem}/{totalmem} MiB")
|
||||
Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem}
|
||||
|
||||
Requires nvidia-smi
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.utilization))
|
||||
self._utilization = "not found: 0°C 0/0 MiB"
|
||||
|
||||
def utilization(self, widget):
|
||||
return self._utilization
|
||||
|
||||
def hidden(self):
|
||||
hide = bumblebee.util.asbool(self.parameter("hide", False))
|
||||
|
||||
if hide and "not found" in self._utilization.startswith("not found"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def update(self, widgets):
|
||||
sp = subprocess.Popen(['nvidia-smi', '-q'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out_str = sp.communicate()
|
||||
out_list = out_str[0].decode("utf-8").split('\n')
|
||||
|
||||
title = ""
|
||||
usedMem = ""
|
||||
totalMem = ""
|
||||
temp = ""
|
||||
name = "not found"
|
||||
clockMem = ""
|
||||
clockGpu = ""
|
||||
fanspeed = ""
|
||||
for item in out_list:
|
||||
try:
|
||||
key, val = item.split(':')
|
||||
key, val = key.strip(), val.strip()
|
||||
if title == "Clocks":
|
||||
if key == "Graphics":
|
||||
clockGpu = val.split(" ")[0]
|
||||
elif key == "Memory":
|
||||
clockMem = val.split(" ")[0]
|
||||
if title == "FB Memory Usage":
|
||||
if key == "Total":
|
||||
totalMem = val.split(" ")[0]
|
||||
elif key == "Used":
|
||||
usedMem = val.split(" ")[0]
|
||||
elif key == "GPU Current Temp":
|
||||
temp = val.split(" ")[0]
|
||||
elif key == "Product Name":
|
||||
name = val
|
||||
elif key == "Fan Speed":
|
||||
fanspeed = val.split(" ")[0]
|
||||
|
||||
except:
|
||||
title = item.strip()
|
||||
|
||||
str_format = self.parameter("format", '{name}: {temp}°C {mem_used}/{mem_total} MiB')
|
||||
self._utilization = str_format.format(
|
||||
name = name,
|
||||
temp = temp,
|
||||
mem_used = usedMem,
|
||||
mem_total = totalMem,
|
||||
clock_gpu = clockGpu,
|
||||
clock_mem = clockMem,
|
||||
fanspeed = fanspeed,
|
||||
)
|
|
@ -1,79 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays update information per repository for pacman.
|
||||
|
||||
Parameters:
|
||||
* pacman.sum: If you prefere displaying updates with a single digit (defaults to "False")
|
||||
|
||||
Requires the following executables:
|
||||
* fakeroot
|
||||
* pacman
|
||||
"""
|
||||
|
||||
import os
|
||||
import threading
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
#list of repositories.
|
||||
#the last one should always be other
|
||||
repos = ["core", "extra", "community", "multilib", "testing", "other"]
|
||||
|
||||
def get_pacman_info(widget, path):
|
||||
try:
|
||||
cmd = "{}/../../bin/pacman-updates".format(path)
|
||||
if not os.path.exists(cmd):
|
||||
cmd = "/usr/share/bumblebee-status/bin/pacman-update"
|
||||
result = bumblebee.util.execute(cmd)
|
||||
except:
|
||||
pass
|
||||
|
||||
count = len(repos)*[0]
|
||||
|
||||
for line in result.splitlines():
|
||||
if line.startswith(("http", "rsync")):
|
||||
for i in range(len(repos)-1):
|
||||
if "/" + repos[i] + "/" in line:
|
||||
count[i] += 1
|
||||
break
|
||||
else:
|
||||
result[-1] += 1
|
||||
|
||||
for i in range(len(repos)):
|
||||
widget.set(repos[i], count[i])
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.updates)
|
||||
)
|
||||
self._count = 0
|
||||
|
||||
def updates(self, widget):
|
||||
if bumblebee.util.asbool(self.parameter("sum")):
|
||||
return str(sum(map(lambda x: widget.get(x, 0), repos)))
|
||||
return '/'.join(map(lambda x: str(widget.get(x, 0)), repos))
|
||||
|
||||
def update(self, widgets):
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
if self._count == 0:
|
||||
thread = threading.Thread(target=get_pacman_info, args=(widgets[0], path))
|
||||
thread.start()
|
||||
|
||||
# TODO: improve this waiting mechanism a bit
|
||||
self._count += 1
|
||||
self._count = 0 if self._count > 300 else self._count
|
||||
|
||||
def state(self, widget):
|
||||
weightedCount = sum(map(lambda x: (len(repos)-x[0]) * widget.get(x[1], 0), enumerate(repos)))
|
||||
|
||||
if weightedCount < 10:
|
||||
return "good"
|
||||
|
||||
return self.threshold_state(weightedCount, 100, 150)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,68 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the pi-hole status (up/down) together with the number of ads that were blocked today
|
||||
Parameters:
|
||||
* pihole.address : pi-hole address (e.q: http://192.168.1.3)
|
||||
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import requests
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.pihole_status)
|
||||
)
|
||||
|
||||
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE}
|
||||
self._pihole_address = self.parameter("address", "")
|
||||
|
||||
self._pihole_pw_hash = self.parameter("pwhash", "")
|
||||
self._pihole_status = None
|
||||
self._ads_blocked_today = "-"
|
||||
self.update_pihole_status()
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.toggle_pihole_status)
|
||||
|
||||
def pihole_status(self, widget):
|
||||
if self._pihole_status is None:
|
||||
return "pi-hole unknown"
|
||||
return "pi-hole " + ("up/" + self._ads_blocked_today if self._pihole_status else "down")
|
||||
|
||||
def update_pihole_status(self):
|
||||
try:
|
||||
data = requests.get(self._pihole_address + "/admin/api.php?summary").json()
|
||||
self._pihole_status = True if data["status"] == "enabled" else False
|
||||
self._ads_blocked_today = data["ads_blocked_today"]
|
||||
except:
|
||||
self._pihole_status = None
|
||||
|
||||
def toggle_pihole_status(self, widget):
|
||||
if self._pihole_status is not None:
|
||||
try:
|
||||
req = None
|
||||
if self._pihole_status:
|
||||
req = requests.get(self._pihole_address + "/admin/api.php?disable&auth=" + self._pihole_pw_hash)
|
||||
else:
|
||||
req = requests.get(self._pihole_address + "/admin/api.php?enable&auth=" + self._pihole_pw_hash)
|
||||
if req is not None:
|
||||
if req.status_code == 200:
|
||||
status = req.json()["status"]
|
||||
self._pihole_status = False if status == "disabled" else True
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def update(self, widgets):
|
||||
self.update_pihole_status()
|
||||
|
||||
def state(self, widget):
|
||||
if self._pihole_status is None:
|
||||
return []
|
||||
elif self._pihole_status:
|
||||
return ["enabled"]
|
||||
return ["disabled", "warning"]
|
|
@ -1,84 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Periodically checks the RTT of a configurable host using ICMP echos
|
||||
|
||||
Requires the following executable:
|
||||
* ping
|
||||
|
||||
Parameters:
|
||||
* ping.interval: Time in seconds between two RTT checks (defaults to 60)
|
||||
* ping.address : IP address to check
|
||||
* ping.timeout : Timeout for waiting for a reply (defaults to 5.0)
|
||||
* ping.probes : Number of probes to send (defaults to 5)
|
||||
* ping.warning : Threshold for warning state, in seconds (defaults to 1.0)
|
||||
* ping.critical: Threshold for critical state, in seconds (defaults to 2.0)
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import threading
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
def get_rtt(module, widget):
|
||||
try:
|
||||
widget.set("rtt-unreachable", False)
|
||||
res = bumblebee.util.execute("ping -n -q -c {} -W {} {}".format(
|
||||
widget.get("rtt-probes"), widget.get("rtt-timeout"), widget.get("address")
|
||||
))
|
||||
|
||||
for line in res.split("\n"):
|
||||
if line.startswith("{} packets transmitted".format(widget.get("rtt-probes"))):
|
||||
m = re.search(r'(\d+)% packet loss', line)
|
||||
|
||||
widget.set('packet-loss', m.group(1))
|
||||
|
||||
if not line.startswith("rtt"): continue
|
||||
m = re.search(r'([0-9\.]+)/([0-9\.]+)/([0-9\.]+)/([0-9\.]+)\s+(\S+)', line)
|
||||
|
||||
widget.set("rtt-min", float(m.group(1)))
|
||||
widget.set("rtt-avg", float(m.group(2)))
|
||||
widget.set("rtt-max", float(m.group(3)))
|
||||
widget.set("rtt-unit", m.group(5))
|
||||
except Exception as e:
|
||||
widget.set("rtt-unreachable", True)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.rtt)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
|
||||
widget.set("address", self.parameter("address", "8.8.8.8"))
|
||||
widget.set("interval", self.parameter("interval", 60))
|
||||
widget.set("rtt-probes", self.parameter("probes", 5))
|
||||
widget.set("rtt-timeout", self.parameter("timeout", 5.0))
|
||||
widget.set("rtt-avg", 0.0)
|
||||
widget.set("rtt-unit", "")
|
||||
widget.set('packet-loss', 0)
|
||||
|
||||
self._next_check = 0
|
||||
|
||||
def rtt(self, widget):
|
||||
if widget.get("rtt-unreachable"):
|
||||
return "{}: unreachable".format(widget.get("address"))
|
||||
return "{}: {:.1f}{} ({}%)".format(
|
||||
widget.get("address"),
|
||||
widget.get("rtt-avg"),
|
||||
widget.get("rtt-unit"),
|
||||
widget.get('packet-loss')
|
||||
)
|
||||
|
||||
def state(self, widget):
|
||||
if widget.get("rtt-unreachable"): return ["critical"]
|
||||
return self.threshold_state(widget.get("rtt-avg"), 1000.0, 2000.0)
|
||||
|
||||
def update(self, widgets):
|
||||
if int(time.time()) < self._next_check:
|
||||
return
|
||||
thread = threading.Thread(target=get_rtt, args=(self, widgets[0],))
|
||||
thread.start()
|
||||
self._next_check = int(time.time()) + int(widgets[0].get("interval"))
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,103 +0,0 @@
|
|||
"""
|
||||
Show progress for cp, mv, dd, ...
|
||||
|
||||
Parameters:
|
||||
* progress.placeholder: Text to display while no process is running (defaults to "n/a")
|
||||
* progress.barwidth: Width of the progressbar if it is used (defaults to 8)
|
||||
* progress.format: Format string (defaults to "{bar} {cmd} {arg}")
|
||||
Available values are: {bar} {pid} {cmd} {arg} {percentage} {quantity} {speed} {time}
|
||||
* progress.barfilledchar: Character used to draw the filled part of the bar (defaults to "#"), notice that it can be a string
|
||||
* progress.baremptychar: Character used to draw the empty part of the bar (defaults to "-"), notice that it can be a string
|
||||
|
||||
Requires the following executable:
|
||||
* progress
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.get_progress_text)
|
||||
)
|
||||
|
||||
def get_progress_text(self, widget):
|
||||
if self.update_progress_info(widget):
|
||||
width = self.parameter("barwidth", 8)
|
||||
count = round((width * widget.get("per")) / 100)
|
||||
filledchar = self.parameter("barfilledchar", "#")
|
||||
emptychar = self.parameter("baremptychar", "-")
|
||||
|
||||
bar = "[{}{}]".format(
|
||||
filledchar * count,
|
||||
emptychar * (width - count)
|
||||
)
|
||||
|
||||
str_format = self.parameter("format", '{bar} {cmd} {arg}')
|
||||
return str_format.format(
|
||||
bar = bar,
|
||||
pid = widget.get("pid"),
|
||||
cmd = widget.get("cmd"),
|
||||
arg = widget.get("arg"),
|
||||
percentage = widget.get("per"),
|
||||
quantity = widget.get("qty"),
|
||||
speed = widget.get("spd"),
|
||||
time = widget.get("tim")
|
||||
)
|
||||
else:
|
||||
return self.parameter("placeholder", 'n/a')
|
||||
|
||||
def update_progress_info(self, widget):
|
||||
"""Update widget's informations about the copy"""
|
||||
|
||||
# These regex extracts following groups:
|
||||
# 1. pid
|
||||
# 2. command
|
||||
# 3. arguments
|
||||
# 4. progress (xx.x formated)
|
||||
# 5. quantity (.. unit / .. unit formated)
|
||||
# 6. speed
|
||||
# 7. time remaining
|
||||
extract_nospeed = re.compile("\[ *(\d*)\] ([a-zA-Z]*) (.*)\n\t(\d*\.*\d*)% \((.*)\)\n.*")
|
||||
extract_wtspeed = re.compile('\[ *(\d*)\] ([a-zA-Z]*) (.*)\n\t(\d*\.*\d*)% \((.*)\) (\d*\.\d .*) remaining (\d*:\d*:\d*)\n.*')
|
||||
|
||||
try:
|
||||
raw = bumblebee.util.execute("progress -qW 0.1")
|
||||
result = extract_wtspeed.match(raw)
|
||||
|
||||
if not result:
|
||||
# Abord speed measures
|
||||
raw = bumblebee.util.execute("progress -q")
|
||||
result = extract_nospeed.match(raw)
|
||||
|
||||
widget.set("spd", "???.? B/s")
|
||||
widget.set("tim", "??:??:??")
|
||||
else:
|
||||
widget.set("spd", result.group(6))
|
||||
widget.set("tim", result.group(7))
|
||||
|
||||
widget.set("pid", int(result.group(1)))
|
||||
widget.set("cmd", result.group(2))
|
||||
widget.set("arg", result.group(3))
|
||||
widget.set("per", float(result.group(4)))
|
||||
widget.set("qty", result.group(5))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def state(self, widget):
|
||||
if self._active():
|
||||
return "copying"
|
||||
return "pending"
|
||||
|
||||
def _active(self):
|
||||
"""Checks wether a copy is running or not"""
|
||||
raw = bumblebee.util.execute("progress -q")
|
||||
return bool(raw)
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
"""Displays public IP address
|
||||
|
||||
Requires the following python packages:
|
||||
* requests
|
||||
|
||||
Parameters:
|
||||
* publicip.region: us-central (default), us-east, us-west, uk, de, pl, nl
|
||||
* publicip.service: web address that returns plaintext ip address (ex. "http://l2.io/ip")
|
||||
"""
|
||||
|
||||
try:
|
||||
from requests import get
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.public_ip)
|
||||
)
|
||||
self._avail_regions = {"us-east":"http://checkip.amazonaws.com",
|
||||
"us-central":"http://whatismyip.akamai.com",
|
||||
"us-west":"http://ipv4bot.whatismyipaddress.com",
|
||||
"pl":"http://ip.42.pl/raw",
|
||||
"de":"http://myexternalip.com/raw",
|
||||
"nl":"http://tnx.nl/ip",
|
||||
"uk":"http://ident.me"}
|
||||
self._region = self.parameter("region", "us-central")
|
||||
self._service = self.parameter("service", "")
|
||||
self._ip = ""
|
||||
|
||||
|
||||
def public_ip(self, widget):
|
||||
return self._ip
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
if self._service:
|
||||
self.address = self._service
|
||||
else:
|
||||
self.address = self._avail_regions[self._region]
|
||||
self._ip = get(self.address).text.rstrip()
|
||||
except Exception:
|
||||
self._ip = "No Connection"
|
|
@ -1,186 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
|
||||
|
||||
Aliases: pasink (use this to control output instead of input), pasource
|
||||
|
||||
Parameters:
|
||||
* pulseaudio.autostart: If set to "true" (default is "false"), automatically starts the pulseaudio daemon if it is not running
|
||||
* pulseaudio.percent_change: How much to change volume by when scrolling on the module (default is 2%)
|
||||
* pulseaudio.limit: Upper limit for setting the volume (default is 0%, which means "no limit")
|
||||
Note: If the left and right channels have different volumes, the limit might not be reached exactly.
|
||||
* pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango;
|
||||
0 for not showing volume bars (default)
|
||||
|
||||
Requires the following executable:
|
||||
* pulseaudio
|
||||
* pactl
|
||||
* pavucontrol
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
def autostart_daemon():
|
||||
try:
|
||||
bumblebee.util.execute("pulseaudio --check")
|
||||
except Exception:
|
||||
try:
|
||||
bumblebee.util.execute("pulseaudio --start")
|
||||
except:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.volume)
|
||||
)
|
||||
if bumblebee.util.asbool(self.parameter("autostart", False)):
|
||||
autostart_daemon()
|
||||
|
||||
self._change = 2
|
||||
self._change = int(self.parameter("percent_change", "2%").strip("%"))
|
||||
if self._change < 0 or self._change > 100:
|
||||
self._change = 2
|
||||
|
||||
self._limit = 0
|
||||
self._limit = int(self.parameter("limit", "0%").strip("%s"))
|
||||
if self._limit < 0:
|
||||
self._limit = 0
|
||||
|
||||
self._left = 0
|
||||
self._right = 0
|
||||
self._mono = 0
|
||||
self._mute = False
|
||||
self._failed = False
|
||||
self._channel = "sink" if self.name == "pasink" else "source"
|
||||
self._showbars = bumblebee.util.asbool(self.parameter("showbars", 0))
|
||||
|
||||
self._patterns = [
|
||||
{"expr": "Name:", "callback": (lambda line: False)},
|
||||
{"expr": "Mute:", "callback": (lambda line: self.mute(False if " no" in line.lower() else True))},
|
||||
{"expr": "Volume:", "callback": self.getvolume},
|
||||
]
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd="pavucontrol")
|
||||
|
||||
events = [
|
||||
{"type": "mute", "action": self.toggle, "button": bumblebee.input.LEFT_MOUSE},
|
||||
{"type": "volume", "action": self.increase_volume, "button": bumblebee.input.WHEEL_UP},
|
||||
{"type": "volume", "action": self.decrease_volume, "button": bumblebee.input.WHEEL_DOWN},
|
||||
]
|
||||
|
||||
for event in events:
|
||||
engine.input.register_callback(self, button=event["button"], cmd=event["action"])
|
||||
|
||||
def set_volume(self, amount):
|
||||
bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format(
|
||||
self._channel, "volume", self._channel.upper(), amount))
|
||||
|
||||
def increase_volume(self, event):
|
||||
if self._limit > 0: # we need to check the limit
|
||||
left = int(self._left)
|
||||
right = int(self._right)
|
||||
if left + self._change >= self._limit or right + self._change >= self._limit:
|
||||
if left == right:
|
||||
# easy case, just set to limit
|
||||
self.set_volume("{}%".format(self._limit))
|
||||
return
|
||||
else:
|
||||
# don't adjust anymore, since i don't know how to update only one channel
|
||||
return
|
||||
|
||||
self.set_volume("+{}%".format(self._change))
|
||||
|
||||
def decrease_volume(self, event):
|
||||
self.set_volume("-{}%".format(self._change))
|
||||
|
||||
def toggle(self, event):
|
||||
bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format(
|
||||
self._channel, "mute", self._channel.upper(), "toggle"))
|
||||
|
||||
def mute(self, value):
|
||||
self._mute = value
|
||||
|
||||
def getvolume(self, line):
|
||||
if "mono" in line:
|
||||
m = re.search(r'mono:.*\s*\/\s*(\d+)%', line)
|
||||
if m:
|
||||
self._mono = m.group(1)
|
||||
else:
|
||||
m = re.search(r'left:.*\s*\/\s*(\d+)%.*right:.*\s*\/\s*(\d+)%', line)
|
||||
if m:
|
||||
self._left = m.group(1)
|
||||
self._right = m.group(2)
|
||||
return True
|
||||
|
||||
def _default_device(self):
|
||||
output = bumblebee.util.execute("pactl info")
|
||||
pattern = "Default {}: ".format("Sink" if self.name == "pasink" else "Source")
|
||||
for line in output.split("\n"):
|
||||
if line.startswith(pattern):
|
||||
return line.replace(pattern, "")
|
||||
return "n/a"
|
||||
|
||||
def volume(self, widget):
|
||||
if self._failed == True:
|
||||
return "n/a"
|
||||
if int(self._mono) > 0:
|
||||
vol = "{}%".format(self._mono)
|
||||
if self._showbars:
|
||||
vol = "{} {}".format(
|
||||
vol, bumblebee.output.hbar(float(self._mono)))
|
||||
return vol
|
||||
elif self._left == self._right:
|
||||
vol = "{}%".format(self._left)
|
||||
if self._showbars:
|
||||
vol = "{} {}".format(
|
||||
vol, bumblebee.output.hbar(float(self._left)))
|
||||
return vol
|
||||
else:
|
||||
vol = "{}%/{}%".format(self._left, self._right)
|
||||
if self._showbars:
|
||||
vol = "{} {}{}".format(
|
||||
vol,
|
||||
bumblebee.output.hbar(float(self._left)),
|
||||
bumblebee.output.hbar(float(self._right)))
|
||||
return vol
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
self._failed = False
|
||||
channel = "sinks" if self.name == "pasink" else "sources"
|
||||
device = self._default_device()
|
||||
|
||||
result = bumblebee.util.execute("pactl list {}".format(channel))
|
||||
found = False
|
||||
|
||||
for line in result.split("\n"):
|
||||
if "Name: {}".format(device) in line:
|
||||
found = True
|
||||
continue
|
||||
if found is False:
|
||||
continue
|
||||
for pattern in self._patterns:
|
||||
if not pattern["expr"] in line:
|
||||
continue
|
||||
if pattern["callback"](line) is False and found == True:
|
||||
return
|
||||
except Exception:
|
||||
self._failed = True
|
||||
if bumblebee.util.asbool(self.parameter("autostart", False)):
|
||||
autostart_daemon()
|
||||
self.update(widgets)
|
||||
|
||||
def state(self, widget):
|
||||
if self._mute:
|
||||
return ["warning", "muted"]
|
||||
if int(self._left) > int(100):
|
||||
return ["critical", "unmuted"]
|
||||
return ["unmuted"]
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,129 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current color temperature of redshift
|
||||
|
||||
Requires the following executable:
|
||||
* redshift
|
||||
|
||||
Parameters:
|
||||
* redshift.location : location provider, either of "geoclue2" (default), \
|
||||
"ipinfo" (requires the requests package), or "manual"
|
||||
* redshift.lat : latitude if location is set to "manual"
|
||||
* redshift.lon : longitude if location is set to "manual"
|
||||
"""
|
||||
|
||||
import threading
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
def is_terminated():
|
||||
for thread in threading.enumerate():
|
||||
if thread.name == "MainThread" and not thread.is_alive():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_redshift_value(widget, location, lat, lon):
|
||||
while True:
|
||||
if is_terminated():
|
||||
return
|
||||
widget.get("condition").acquire()
|
||||
while True:
|
||||
try:
|
||||
widget.get("condition").wait(1)
|
||||
except RuntimeError:
|
||||
continue
|
||||
break
|
||||
widget.get("condition").release()
|
||||
|
||||
command = ["redshift", "-p", "-l"]
|
||||
if location == "manual":
|
||||
command.append(lat + ":" + lon)
|
||||
else:
|
||||
command.append("geoclue2")
|
||||
|
||||
try:
|
||||
res = bumblebee.util.execute(" ".join(command))
|
||||
except Exception:
|
||||
res = ""
|
||||
widget.set("temp", "n/a")
|
||||
widget.set("transition", None)
|
||||
widget.set("state", "day")
|
||||
for line in res.split("\n"):
|
||||
line = line.lower()
|
||||
if "temperature" in line:
|
||||
widget.set("temp", line.split(" ")[2])
|
||||
if "period" in line:
|
||||
state = line.split(" ")[1]
|
||||
if "day" in state:
|
||||
widget.set("state", "day")
|
||||
elif "night" in state:
|
||||
widget.set("state", "night")
|
||||
else:
|
||||
widget.set("state", "transition")
|
||||
widget.set("transition", " ".join(line.split(" ")[2:]))
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.text)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
|
||||
self._location = self.parameter("location", "geoclue2")
|
||||
self._lat = self.parameter("lat", None)
|
||||
self._lon = self.parameter("lon", None)
|
||||
|
||||
# Even if location method is set to manual, if we have no lat or lon,
|
||||
# fall back to the geoclue2 method.
|
||||
if self._location == "manual" and (self._lat is None
|
||||
or self._lon is None):
|
||||
self._location == "geoclue2"
|
||||
|
||||
if self._location == "ipinfo":
|
||||
try:
|
||||
location_url = "http://ipinfo.io/json"
|
||||
location = requests.get(location_url).json()
|
||||
self._lat, self._lon = location["loc"].split(",")
|
||||
self._lat = str(float(self._lat))
|
||||
self._lon = str(float(self._lon))
|
||||
self._location = "manual"
|
||||
except Exception:
|
||||
# Fall back to geoclue2.
|
||||
self._location = "geoclue2"
|
||||
|
||||
self._text = ""
|
||||
self._condition = threading.Condition()
|
||||
widget.set("condition", self._condition)
|
||||
self._thread = threading.Thread(target=get_redshift_value,
|
||||
args=(widget, self._location,
|
||||
self._lat, self._lon))
|
||||
self._thread.start()
|
||||
self._condition.acquire()
|
||||
self._condition.notify()
|
||||
self._condition.release()
|
||||
|
||||
def text(self, widget):
|
||||
return "{}".format(self._text)
|
||||
|
||||
def update(self, widgets):
|
||||
widget = widgets[0]
|
||||
self._condition.acquire()
|
||||
self._condition.notify()
|
||||
self._condition.release()
|
||||
temp = widget.get("temp", "n/a")
|
||||
self._text = temp
|
||||
transition = widget.get("transition", None)
|
||||
if transition:
|
||||
self._text = "{} {}".format(temp, transition)
|
||||
|
||||
def state(self, widget):
|
||||
return widget.get("state", None)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,128 +0,0 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays sensor temperature
|
||||
|
||||
Parameters:
|
||||
* sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp).
|
||||
* sensors.json: if set to "true", interpret sensors.path as JSON "path" in the output
|
||||
of "sensors -j" (i.e. <key1>/<key2>/.../<value>), for example, path could
|
||||
be: "coretemp-isa-00000/Core 0/temp1_input" (defaults to "false")
|
||||
* sensors.match: (fallback) Line to match against output of 'sensors -u' (default: temp1_input)
|
||||
* sensors.match_pattern: (fallback) Line to match against before temperature is read (no default)
|
||||
* sensors.match_number: (fallback) which of the matches you want (default -1: last match).
|
||||
* sensors.show_freq: whether to show CPU frequency. (default: true)
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.temperature))
|
||||
self._temperature = "unknown"
|
||||
self._mhz = "n/a"
|
||||
self._match_number = int(self.parameter("match_number", "-1"))
|
||||
self._match_pattern = self.parameter("match_pattern", None)
|
||||
self._pattern = re.compile(r"^\s*{}:\s*([\d.]+)$".format(self.parameter("match", "temp1_input")), re.MULTILINE)
|
||||
self._json = bumblebee.util.asbool(self.parameter("json", "false"))
|
||||
self._freq = bumblebee.util.asbool(self.parameter("show_freq", "true"))
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd="xsensors")
|
||||
self.determine_method()
|
||||
|
||||
def determine_method(self):
|
||||
if self.parameter("path") != None and self._json == False:
|
||||
self.use_sensors = False # use thermal zone
|
||||
else:
|
||||
# try to use output of sensors -u
|
||||
try:
|
||||
output = bumblebee.util.execute("sensors -u")
|
||||
self.use_sensors = True
|
||||
log.debug("Sensors command available")
|
||||
except FileNotFoundError as e:
|
||||
log.info("Sensors command not available, using /sys/class/thermal/thermal_zone*/")
|
||||
self.use_sensors = False
|
||||
|
||||
def _get_temp_from_sensors(self):
|
||||
if self._json == True:
|
||||
try:
|
||||
output = json.loads(bumblebee.util.execute("sensors -j"))
|
||||
for key in self.parameter("path").split("/"):
|
||||
output = output[key]
|
||||
return int(float(output))
|
||||
except Exception as e:
|
||||
logging.error("unable to read sensors: {}".format(str(e)))
|
||||
return "unknown"
|
||||
else:
|
||||
output = bumblebee.util.execute("sensors -u")
|
||||
if self._match_pattern:
|
||||
temp_pattern = self.parameter("match", "temp1_input")
|
||||
match = re.search(r"{}.+{}:\s*([\d.]+)$".format(self._match_pattern, temp_pattern), output.replace("\n", ""))
|
||||
if match:
|
||||
return int(float(match.group(1)))
|
||||
else:
|
||||
return "unknown"
|
||||
match = self._pattern.findall(output)
|
||||
if match:
|
||||
return int(float(match[self._match_number]))
|
||||
return "unknown"
|
||||
|
||||
def get_temp(self):
|
||||
if self.use_sensors:
|
||||
temperature = self._get_temp_from_sensors()
|
||||
log.debug("Retrieve temperature from sensors -u")
|
||||
else:
|
||||
try:
|
||||
temperature = open(self.parameter("path", "/sys/class/thermal/thermal_zone0/temp")).read()[:2]
|
||||
log.debug("retrieved temperature from /sys/class/")
|
||||
# TODO: Iterate through all thermal zones to determine the correct one and use its value
|
||||
# https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal
|
||||
|
||||
except IOError:
|
||||
temperature = "unknown"
|
||||
log.info("Can not determine temperature, please install lm-sensors")
|
||||
|
||||
return temperature
|
||||
|
||||
def get_mhz(self):
|
||||
mhz = None
|
||||
try:
|
||||
output = open("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq").read()
|
||||
mhz = int(float(output)/1000.0)
|
||||
except:
|
||||
output = open("/proc/cpuinfo").read()
|
||||
m = re.search(r"cpu MHz\s+:\s+(\d+)", output)
|
||||
if m:
|
||||
mhz = int(m.group(1))
|
||||
else:
|
||||
m = re.search(r"BogoMIPS\s+:\s+(\d+)", output)
|
||||
if m:
|
||||
return "{} BogoMIPS".format(int(m.group(1)))
|
||||
if not mhz:
|
||||
return "n/a"
|
||||
|
||||
if mhz < 1000:
|
||||
return "{} MHz".format(mhz)
|
||||
else:
|
||||
return "{:0.01f} GHz".format(float(mhz)/1000.0)
|
||||
|
||||
def temperature(self, _):
|
||||
if self._freq:
|
||||
return u"{}°c @ {}".format(self._temperature, self._mhz)
|
||||
else:
|
||||
return u"{}°c".format(self._temperature)
|
||||
def update(self, widgets):
|
||||
self._temperature = self.get_temp()
|
||||
if self._freq:
|
||||
self._mhz = self.get_mhz()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,203 +0,0 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""Displays sensor temperature and CPU frequency
|
||||
|
||||
Parameters:
|
||||
|
||||
* sensors2.chip: "sensors -u" compatible filter for chip to display (default to empty - show all chips)
|
||||
* sensors2.showcpu: Enable or disable CPU frequency display (default: true)
|
||||
* sensors2.showtemp: Enable or disable temperature display (default: true)
|
||||
* sensors2.showfan: Enable or disable fan display (default: true)
|
||||
* sensors2.showother: Enable or display "other" sensor readings (default: false)
|
||||
* sensors2.showname: Enable or disable show of sensor name (default: false)
|
||||
* sensors2.chip_include: Comma-separated list of chip to include (defaults to "" will include all by default, example: "coretemp,bat")
|
||||
* sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to "" will exlude none by default)
|
||||
* sensors2.field_include: Comma separated list of chip to include (defaults to "" will include all by default, example: "temp,fan")
|
||||
* sensors2.field_exclude: Comma separated list of chip to exclude (defaults to "" will exclude none by default)
|
||||
* sensors2.chip_field_exclude: Comma separated list of chip field to exclude (defaults to "" will exclude none by default, example: "coretemp-isa-0000.temp1,coretemp-isa-0000.fan1")
|
||||
* sensors2.chip_field_include: Comma-separated list of adaper field to include (defaults to "" will include all by default)
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config, None)
|
||||
self._chip = self.parameter("chip", "")
|
||||
self._data = {}
|
||||
self._update()
|
||||
|
||||
self.widgets(self._create_widgets())
|
||||
|
||||
def update(self, widgets):
|
||||
self._update()
|
||||
for widget in widgets:
|
||||
self._update_widget(widget)
|
||||
|
||||
def state(self, widget):
|
||||
widget_type = widget.get("type", "")
|
||||
try:
|
||||
data = self._data[widget.get("adapter")][widget.get("package")][widget.get("field")]
|
||||
if "crit" in data and float(data["input"]) > float(data["crit"]):
|
||||
return ["critical", widget_type]
|
||||
if "max" in data and float(data["input"]) > float(data["max"]):
|
||||
return ["warning", widget_type]
|
||||
except:
|
||||
pass
|
||||
return [widget_type]
|
||||
|
||||
def _create_widgets(self):
|
||||
widgets = []
|
||||
show_temp = bumblebee.util.asbool(self.parameter("showtemp", "true"))
|
||||
show_fan = bumblebee.util.asbool(self.parameter("showfan", "true"))
|
||||
show_other = bumblebee.util.asbool(self.parameter("showother", "false"))
|
||||
include_chip = tuple(filter(len, self.parameter("chip_include", "").split(",")))
|
||||
exclude_chip = tuple(filter(len, self.parameter("chip_exclude", "").split(",")))
|
||||
include_field = tuple(filter(len, self.parameter("field_include", "").split(",")))
|
||||
exclude_field = tuple(filter(len, self.parameter("field_exclude", "").split(",")))
|
||||
include_chip_field = tuple(filter(len, self.parameter("chip_field_include", "").split(",")))
|
||||
exclude_chip_field = tuple(filter(len, self.parameter("chip_field_exclude", "").split(",")))
|
||||
|
||||
if bumblebee.util.asbool(self.parameter("showcpu", "true")):
|
||||
widget = bumblebee.output.Widget(full_text=self._cpu)
|
||||
widget.set("type", "cpu")
|
||||
widgets.append(widget)
|
||||
|
||||
for adapter in self._data:
|
||||
if include_chip or exclude_chip:
|
||||
if include_chip:
|
||||
if all([chip not in adapter for chip in include_chip]):
|
||||
continue
|
||||
else:
|
||||
if any([chip in adapter for chip in exclude_chip]):
|
||||
continue
|
||||
|
||||
if include_chip_field:
|
||||
try:
|
||||
if all([i.split('.')[0] not in adapter for i in include_chip_field]):
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
|
||||
for package in self._data[adapter]:
|
||||
if bumblebee.util.asbool(self.parameter("showname", "false")):
|
||||
widget = bumblebee.output.Widget(full_text=package)
|
||||
widget.set("data", self._data[adapter][package])
|
||||
widget.set("package", package)
|
||||
widget.set("field", "")
|
||||
widget.set("adapter", adapter)
|
||||
widgets.append(widget)
|
||||
for field in self._data[adapter][package]:
|
||||
|
||||
if include_field or exclude_field:
|
||||
if include_field:
|
||||
if all([included not in field for included in include_field]):
|
||||
continue
|
||||
else:
|
||||
if any([excluded in field for excluded in exclude_field]):
|
||||
continue
|
||||
|
||||
try:
|
||||
if include_chip_field or exclude_chip_field:
|
||||
if include_chip_field:
|
||||
if all([i.split('.')[1] not in field for i in include_chip_field if i.split('.')[0] in adapter]):
|
||||
continue
|
||||
else:
|
||||
if any([i.split('.')[1] in field for i in exclude_chip_field if i.split('.')[0] in adapter]):
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
|
||||
widget = bumblebee.output.Widget()
|
||||
widget.set("package", package)
|
||||
widget.set("field", field)
|
||||
widget.set("adapter", adapter)
|
||||
if "temp" in field and show_temp:
|
||||
# seems to be a temperature
|
||||
widget.set("type", "temp")
|
||||
widgets.append(widget)
|
||||
if "fan" in field and show_fan:
|
||||
# seems to be a fan
|
||||
widget.set("type", "fan")
|
||||
widgets.append(widget)
|
||||
elif show_other:
|
||||
# everything else
|
||||
widget.set("type", "other")
|
||||
widgets.append(widget)
|
||||
return widgets
|
||||
|
||||
def _update_widget(self, widget):
|
||||
if widget.get("field", "") == "":
|
||||
return # nothing to do
|
||||
data = self._data[widget.get("adapter")][widget.get("package")][widget.get("field")]
|
||||
if "temp" in widget.get("field"):
|
||||
widget.full_text(u"{:0.01f}°C".format(data["input"]))
|
||||
elif "fan" in widget.get("field"):
|
||||
widget.full_text(u"{:0.0f}RPM".format(data["input"]))
|
||||
else:
|
||||
widget.full_text(u"{:0.0f}".format(data["input"]))
|
||||
|
||||
def _update(self):
|
||||
output = bumblebee.util.execute("sensors -u {}".format(self._chip))
|
||||
self._data = self._parse(output)
|
||||
|
||||
def _parse(self, data):
|
||||
output = {}
|
||||
package = ""
|
||||
adapter = None
|
||||
chip = None
|
||||
for line in data.split("\n"):
|
||||
if "Adapter" in line:
|
||||
# new adapter
|
||||
line = line.replace("Adapter: ", "")
|
||||
output[chip + " " + line] = {}
|
||||
adapter = chip + " " + line
|
||||
chip = line #default - line before adapter is always the chip
|
||||
if not adapter: continue
|
||||
key, value = (line.split(":") + ["", ""])[:2]
|
||||
if not line.startswith(" "):
|
||||
# assume this starts a new package
|
||||
if package in output[adapter] and output[adapter][package] == {}:
|
||||
del output[adapter][package]
|
||||
output[adapter][key] = {}
|
||||
package = key
|
||||
else:
|
||||
# feature for this chip
|
||||
try:
|
||||
name, variant = (key.strip().split("_", 1) + ["",""])[:2]
|
||||
if not name in output[adapter][package]:
|
||||
output[adapter][package][name] = { }
|
||||
if variant:
|
||||
output[adapter][package][name][variant] = {}
|
||||
output[adapter][package][name][variant] = float(value)
|
||||
except Exception as e:
|
||||
pass
|
||||
return output
|
||||
|
||||
def _cpu(self, _):
|
||||
mhz = None
|
||||
try:
|
||||
output = open("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq").read()
|
||||
mhz = int(float(output)/1000.0)
|
||||
except:
|
||||
output = open("/proc/cpuinfo").read()
|
||||
m = re.search(r"cpu MHz\s+:\s+(\d+)", output)
|
||||
if m:
|
||||
mhz = int(m.group(1))
|
||||
else:
|
||||
m = re.search(r"BogoMIPS\s+:\s+(\d+)", output)
|
||||
if m:
|
||||
return "{} BogoMIPS".format(int(m.group(1)))
|
||||
if not mhz:
|
||||
return "n/a"
|
||||
|
||||
if mhz < 1000:
|
||||
return "{} MHz".format(mhz)
|
||||
else:
|
||||
return "{:0.01f} GHz".format(float(mhz)/1000.0)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,93 +0,0 @@
|
|||
# pylint: disable=C0111,R0903,W1401
|
||||
|
||||
""" Execute command in shell and print result
|
||||
|
||||
Few command examples:
|
||||
'ping 1.1.1.1 -c 1 | grep -Po "(?<=time=)\d+(\.\d+)? ms"'
|
||||
'echo "BTC=$(curl -s rate.sx/1BTC | grep -Po \"^\d+\")USD"'
|
||||
'curl -s https://wttr.in/London?format=%l+%t+%h+%w'
|
||||
'pip3 freeze | wc -l'
|
||||
'any_custom_script.sh | grep arguments'
|
||||
|
||||
Parameters:
|
||||
* shell.command: Command to execute
|
||||
Use single parentheses if evaluating anything inside (sh-style)
|
||||
For example shell.command='echo $(date +"%H:%M:%S")'
|
||||
But NOT shell.command="echo $(date +'%H:%M:%S')"
|
||||
Second one will be evaluated only once at startup
|
||||
* shell.interval: Update interval in seconds
|
||||
(defaults to 1s == every bumblebee-status update)
|
||||
* shell.async: Run update in async mode. Won't run next thread if
|
||||
previous one didn't finished yet. Useful for long
|
||||
running scripts to avoid bumblebee-status freezes
|
||||
(defaults to False)
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
import bumblebee.engine
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widget = bumblebee.output.Widget(full_text=self.get_output)
|
||||
super(Module, self).__init__(engine, config, widget)
|
||||
|
||||
if self.parameter('interval'):
|
||||
self.interval(self.parameter('interval'))
|
||||
|
||||
self._command = self.parameter('command')
|
||||
self._async = bumblebee.util.asbool(self.parameter('async'))
|
||||
if self._async:
|
||||
self._output = 'Computing...'
|
||||
self._current_thread = None
|
||||
else:
|
||||
self._output = ''
|
||||
|
||||
# LMB and RMB will update output regardless of timer
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update)
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.update)
|
||||
|
||||
def get_output(self, _):
|
||||
return self._output
|
||||
|
||||
def update(self, _):
|
||||
# if requested then run not async version and just execute command in this thread
|
||||
if not self._async:
|
||||
self._output = self._get_command_output_or_error(self._command)
|
||||
return
|
||||
|
||||
# if previous thread didn't end yet then don't do anything
|
||||
if self._current_thread:
|
||||
return
|
||||
|
||||
# spawn new thread to execute command and pass callback method to get output from it
|
||||
self._current_thread = threading.Thread(target=self._run_command_in_thread,
|
||||
args=(self._command, self._output_function))
|
||||
self._current_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def _get_command_output_or_error(command):
|
||||
try:
|
||||
command_output = subprocess.check_output(command,
|
||||
executable=os.environ.get('SHELL'),
|
||||
shell=True,
|
||||
stderr=subprocess.STDOUT)
|
||||
return command_output.decode('utf-8').strip()
|
||||
except subprocess.CalledProcessError as exception:
|
||||
exception_output = exception.output.decode('utf-8').replace('\n', '')
|
||||
return 'Status:{} output:{}'.format(exception.returncode, exception_output)
|
||||
|
||||
def _run_command_in_thread(self, command, output_callback):
|
||||
output_callback(self._get_command_output_or_error(command))
|
||||
|
||||
def _output_function(self, text):
|
||||
self._output = text
|
||||
# clear this thread data, so next update will spawn a new one
|
||||
self._current_thread = None
|
||||
|
||||
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,70 +0,0 @@
|
|||
# pylint: disable=C0112,R0903
|
||||
|
||||
"""Shows a widget per user-defined shortcut and allows to define the behaviour
|
||||
when clicking on it.
|
||||
|
||||
For more than one shortcut, the commands and labels are strings separated by
|
||||
a demiliter (; semicolon by default).
|
||||
|
||||
For example in order to create two shortcuts labeled A and B with commands
|
||||
cmdA and cmdB you could do:
|
||||
|
||||
./bumblebee-status -m shortcut -p shortcut.cmd="ls;ps" shortcut.label="A;B"
|
||||
|
||||
Parameters:
|
||||
* shortcut.cmds : List of commands to execute
|
||||
* shortcut.labels: List of widgets' labels (text)
|
||||
* shortcut.delim : Commands and labels delimiter (; semicolon by default)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import bumblebee.engine
|
||||
import bumblebee.output
|
||||
import bumblebee.input
|
||||
|
||||
LINK = "https://github.com/tobi-wan-kenobi/bumblebee-status/wiki"
|
||||
LABEL = "Click me"
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
""" Shortcut module."""
|
||||
|
||||
def __init__(self, engine, config):
|
||||
widgets = []
|
||||
self._engine = engine
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
|
||||
self._labels = self.parameter("labels", "{}".format(LABEL))
|
||||
self._cmds = self.parameter("cmds", "firefox {}".format(LINK))
|
||||
self._delim = self.parameter("delim", ";")
|
||||
|
||||
self.update_widgets(widgets)
|
||||
|
||||
def update_widgets(self, widgets):
|
||||
""" Creates a set of widget per user define shortcut."""
|
||||
|
||||
cmds = self._cmds.split(self._delim)
|
||||
labels = self._labels.split(self._delim)
|
||||
|
||||
# to be on the safe side create as many widgets as there are data (cmds or labels)
|
||||
num_shortcuts = min(len(cmds), len(labels))
|
||||
|
||||
# report possible problem as a warning
|
||||
if len(cmds) is not len(labels):
|
||||
logging.warning("shortcut: the number of commands does not match "\
|
||||
"the number of provided labels.")
|
||||
logging.warning("cmds : %s, labels : %s", cmds, labels)
|
||||
|
||||
for idx in range(0, num_shortcuts):
|
||||
cmd = cmds[idx]
|
||||
label = labels[idx]
|
||||
|
||||
widget = bumblebee.output.Widget(full_text=label)
|
||||
self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=cmd)
|
||||
|
||||
widgets.append(widget)
|
||||
|
||||
def update(self, widgets):
|
||||
if len(widgets) <= 0:
|
||||
self.update_widgets(widgets)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,119 +0,0 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
|
||||
# smart function inspired by py-SMART https://github.com/freenas/py-SMART
|
||||
# under Copyright (C) 2015 Marc Herndon and GPL2
|
||||
|
||||
"""Displays HDD smart status of different drives or all drives
|
||||
|
||||
Parameters:
|
||||
* smartstatus.display: how to display (defaults to "combined", other choices: "seperate" or "singles")
|
||||
* smartstauts.drives: in the case of singles which drives to display, separated comma list value, multiple accepted (defaults to "sda", example:"sda,sdc")
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from shutil import which
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config, None)
|
||||
self.devices = self.list_devices()
|
||||
self.display = self.parameter('display', 'combined')
|
||||
self.drives = self.parameter('drives', 'sda')
|
||||
self.widgets(self.create_widgets())
|
||||
|
||||
def create_widgets(self):
|
||||
widgets = []
|
||||
if self.display == 'combined':
|
||||
widget = bumblebee.output.Widget()
|
||||
widget.set('device', 'combined')
|
||||
widget.set('assessment', self.combined())
|
||||
self.output(widget)
|
||||
widgets.append(widget)
|
||||
else:
|
||||
for device in self.devices:
|
||||
if self.display == "singles" and device not in self.drives:
|
||||
continue
|
||||
widget = bumblebee.output.Widget()
|
||||
widget.set('device', device)
|
||||
widget.set('assessment', self.smart(device))
|
||||
self.output(widget)
|
||||
widgets.append(widget)
|
||||
return widgets
|
||||
|
||||
def update(self, widgets):
|
||||
for widget in widgets:
|
||||
device = widget.get('device')
|
||||
if device == 'combined':
|
||||
widget.set('assessment', self.combined())
|
||||
self.output(widget)
|
||||
else:
|
||||
widget.set('assessment', self.smart(device))
|
||||
self.output(widget)
|
||||
|
||||
def output(self, widget):
|
||||
device = widget.get('device')
|
||||
assessment = widget.get('assessment')
|
||||
widget.full_text("{}: {}".format(device, assessment))
|
||||
|
||||
def state(self, widget):
|
||||
states = []
|
||||
assessment = widget.get('assessment')
|
||||
if assessment == 'Pre-fail':
|
||||
states.append('warning')
|
||||
if assessment == 'Fail':
|
||||
states.append('critical')
|
||||
return states
|
||||
|
||||
def combined(self):
|
||||
for device in self.devices:
|
||||
result = self.smart(device)
|
||||
if result == 'Fail':
|
||||
return 'Fail'
|
||||
if result == 'Pre-fail':
|
||||
return 'Pre-fail'
|
||||
return 'OK'
|
||||
|
||||
def list_devices(self):
|
||||
for (root, folders, files) in os.walk('/dev'):
|
||||
if root == '/dev':
|
||||
devices = {"".join(filter(lambda i: i.isdigit() == False, file)) for file in files if 'sd' in file}
|
||||
nvme = {file for file in files if('nvme0n' in file and 'p' not in file)}
|
||||
devices.update(nvme)
|
||||
return devices
|
||||
|
||||
def smart(self, disk_name):
|
||||
SMARTCTL_PATH = which('smartctl')
|
||||
assessment = None
|
||||
cmd = Popen(
|
||||
['sudo', SMARTCTL_PATH, '--health', os.path.join('/dev/', disk_name)],
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
)
|
||||
_stdout, _stderr = [i.decode('utf8') for i in cmd.communicate()]
|
||||
_stdout = _stdout.split('\n')
|
||||
line = _stdout[4]
|
||||
if 'SMART' in line:
|
||||
if any([i in line for i in ['PASSED', 'OK']]):
|
||||
assessment = 'OK'
|
||||
else:
|
||||
assessment = 'Fail'
|
||||
|
||||
if assessment == 'OK':
|
||||
cmd = Popen(
|
||||
['sudo', SMARTCTL_PATH, '-A', os.path.join('/dev/', disk_name)],
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
)
|
||||
_stdout, _stderr = [i.decode('utf8') for i in cmd.communicate()]
|
||||
_stdout = _stdout.split('\n')
|
||||
for line in _stdout:
|
||||
if "Pre-fail" in line:
|
||||
assessment = "Pre-fail"
|
||||
return assessment
|
|
@ -1,23 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Draws a widget with configurable text content.
|
||||
|
||||
Parameters:
|
||||
* spacer.text: Widget contents (defaults to empty string)
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.text)
|
||||
)
|
||||
self._text = self.parameter("text", "")
|
||||
|
||||
def text(self, widget):
|
||||
return self._text
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,87 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current song being played
|
||||
Requires the following library:
|
||||
* python-dbus
|
||||
Parameters:
|
||||
* spotify.format: Format string (defaults to "{artist} - {title}")
|
||||
Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus}
|
||||
* spotify.previous: Change binding for previous song (default is left click)
|
||||
* spotify.next: Change binding for next song (default is right click)
|
||||
* spotify.pause: Change binding for toggling pause (default is middle click)
|
||||
Available options for spotify.previous, spotify.next and spotify.pause are:
|
||||
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from bumblebee.output import scrollable
|
||||
|
||||
try:
|
||||
import dbus
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.spotify)
|
||||
)
|
||||
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE,
|
||||
"RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE,
|
||||
"MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE,
|
||||
"SCROLL_UP":bumblebee.input.WHEEL_UP,
|
||||
"SCROLL_DOWN":bumblebee.input.WHEEL_DOWN,
|
||||
}
|
||||
|
||||
self._song = ""
|
||||
self._format = self.parameter("format", "{artist} - {title}")
|
||||
prev_button = self.parameter("previous", "LEFT_CLICK")
|
||||
next_button = self.parameter("next", "RIGHT_CLICK")
|
||||
pause_button = self.parameter("pause", "MIDDLE_CLICK")
|
||||
|
||||
cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify \
|
||||
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player."
|
||||
engine.input.register_callback(self, button=buttons[prev_button],
|
||||
cmd=cmd + "Previous")
|
||||
engine.input.register_callback(self, button=buttons[next_button],
|
||||
cmd=cmd + "Next")
|
||||
engine.input.register_callback(self, button=buttons[pause_button],
|
||||
cmd=cmd + "PlayPause")
|
||||
|
||||
@scrollable
|
||||
def spotify(self, widget):
|
||||
return self.string_song
|
||||
|
||||
def hidden(self):
|
||||
return self.string_song == ""
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
bus = dbus.SessionBus()
|
||||
spotify = bus.get_object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2")
|
||||
spotify_iface = dbus.Interface(spotify, 'org.freedesktop.DBus.Properties')
|
||||
props = spotify_iface.Get('org.mpris.MediaPlayer2.Player', 'Metadata')
|
||||
playback_status = str(spotify_iface.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus'))
|
||||
self._song = self._format.format(album=str(props.get('xesam:album')),
|
||||
title=str(props.get('xesam:title')),
|
||||
artist=','.join(props.get('xesam:artist')),
|
||||
trackNumber=str(props.get('xesam:trackNumber')),
|
||||
playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",)
|
||||
|
||||
except Exception:
|
||||
self._song = ""
|
||||
|
||||
@property
|
||||
def string_song(self):
|
||||
if sys.version_info.major < 3:
|
||||
return unicode(self._song)
|
||||
return str(self._song)
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,61 +0,0 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Display a stock quote from worldtradingdata.com
|
||||
|
||||
Requires the following python packages:
|
||||
* requests
|
||||
|
||||
Parameters:
|
||||
* stock.symbols : Comma-separated list of symbols to fetch
|
||||
* stock.change : Should we fetch change in stock value (defaults to True)
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
import logging
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.value)
|
||||
)
|
||||
self._symbols = self.parameter('symbols', '')
|
||||
self._change = bumblebee.util.asbool(self.parameter('change', True))
|
||||
self._value = None
|
||||
self.interval_factor(60)
|
||||
self.interval(60)
|
||||
|
||||
def value(self, widget):
|
||||
results = []
|
||||
if not self._value:
|
||||
return 'n/a'
|
||||
data = json.loads(self._value)
|
||||
|
||||
for symbol in data['quoteResponse']['result']:
|
||||
valkey = 'regularMarketChange' if self._change else 'regularMarketPrice'
|
||||
sym = 'n/a' if not 'symbol' in symbol else symbol['symbol']
|
||||
currency = 'USD' if not 'currency' in symbol else symbol['currency']
|
||||
val = 'n/a' if not valkey in symbol else '{:.2f}'.format(symbol[valkey])
|
||||
results.append('{} {} {}'.format(sym, val, currency))
|
||||
return u' '.join(results)
|
||||
|
||||
def fetch(self):
|
||||
if self._symbols:
|
||||
url = 'https://query1.finance.yahoo.com/v7/finance/quote?symbols='
|
||||
url += self._symbols + '&fields=regularMarketPrice,currency,regularMarketChange'
|
||||
return requests.get(url).text.strip()
|
||||
else:
|
||||
logging.error('unable to retrieve stock exchange rate')
|
||||
return None
|
||||
|
||||
def update(self, widgets):
|
||||
self._value = self.fetch()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,117 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays sunrise and sunset times
|
||||
|
||||
Requires the following python packages:
|
||||
* requests
|
||||
* suntime
|
||||
|
||||
Parameters:
|
||||
* cpu.lat : Latitude of your location
|
||||
* cpu.lon : Longitude of your location
|
||||
"""
|
||||
|
||||
try:
|
||||
from suntime import Sun, SunTimeException
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from dateutil.tz import tzlocal
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import datetime
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(
|
||||
engine, config,
|
||||
bumblebee.output.Widget(full_text=self.suntimes)
|
||||
)
|
||||
self.interval(3600)
|
||||
self._lat = self.parameter("lat", None)
|
||||
self._lon = self.parameter("lon", None)
|
||||
try:
|
||||
if not self._lat or not self._lon:
|
||||
location_url = "http://ipinfo.io/json"
|
||||
location = requests.get(location_url).json()
|
||||
self._lat, self._lon = location["loc"].split(",")
|
||||
self._lat = float(self._lat)
|
||||
self._lon = float(self._lon)
|
||||
except Exception:
|
||||
pass
|
||||
self.update(None)
|
||||
|
||||
def suntimes(self, _):
|
||||
if self._sunset and self._sunrise:
|
||||
if self._isup:
|
||||
return u"\u21A7{} \u21A5{}".format(
|
||||
self._sunset.strftime('%H:%M'),
|
||||
self._sunrise.strftime('%H:%M'))
|
||||
return u"\u21A5{} \u21A7{}".format(self._sunrise.strftime('%H:%M'),
|
||||
self._sunset.strftime('%H:%M'))
|
||||
return "?"
|
||||
|
||||
def _calculate_times(self):
|
||||
self._isup = False
|
||||
try:
|
||||
if not self._lat or not self._lon:
|
||||
raise()
|
||||
sun = Sun(self._lat, self._lon)
|
||||
except Exception:
|
||||
self._sunrise = None
|
||||
self._sunset = None
|
||||
return
|
||||
|
||||
order_matters = True
|
||||
|
||||
try:
|
||||
self._sunrise = sun.get_local_sunrise_time()
|
||||
except SunTimeException:
|
||||
self._sunrise = "no sunrise"
|
||||
order_matters = False
|
||||
|
||||
try:
|
||||
self._sunset = sun.get_local_sunset_time()
|
||||
except SunTimeException:
|
||||
self._sunset = "no sunset"
|
||||
order_matters = False
|
||||
|
||||
if not order_matters:
|
||||
return
|
||||
|
||||
now = datetime.datetime.now(tz=tzlocal())
|
||||
if now > self._sunset:
|
||||
tomorrow = (now + datetime.timedelta(days=1)).date()
|
||||
try:
|
||||
self._sunrise = sun.get_local_sunrise_time(tomorrow)
|
||||
self._sunset = sun.get_local_sunset_time(tomorrow)
|
||||
except SunTimeException:
|
||||
self._sunrise = "no sunrise"
|
||||
self._sunset = "no sunset"
|
||||
|
||||
elif now > self._sunrise:
|
||||
tomorrow = (now + datetime.timedelta(days=1)).date()
|
||||
try:
|
||||
self._sunrise = sun.get_local_sunrise_time(tomorrow)
|
||||
except SunTimeException:
|
||||
self._sunrise = "no sunrise"
|
||||
return
|
||||
self._isup = True
|
||||
|
||||
def update(self, widgets):
|
||||
if not self._lat or not self._lon:
|
||||
self._sunrise = None
|
||||
self._sunset = None
|
||||
self._calculate_times()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,98 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=C0111,R0903
|
||||
|
||||
""" system module
|
||||
|
||||
adds the possibility to
|
||||
* shutdown
|
||||
* reboot
|
||||
the system.
|
||||
|
||||
Per default a confirmation dialog is shown before the actual action is performed.
|
||||
|
||||
Parameters:
|
||||
* system.confirm: show confirmation dialog before performing any action (default: true)
|
||||
* system.reboot: specify a reboot command (defaults to 'reboot')
|
||||
* system.shutdown: specify a shutdown command (defaults to 'shutdown -h now')
|
||||
* system.logout: specify a logout command (defaults to 'i3exit logout')
|
||||
* system.switch_user: specify a command for switching the user (defaults to 'i3exit switch_user')
|
||||
* system.lock: specify a command for locking the screen (defaults to 'i3exit lock')
|
||||
* system.suspend: specify a command for suspending (defaults to 'i3exit suspend')
|
||||
* system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate')
|
||||
"""
|
||||
|
||||
import logging
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.popup_v2
|
||||
import functools
|
||||
|
||||
try:
|
||||
import Tkinter as tk
|
||||
import tkMessageBox as tkmessagebox
|
||||
except ImportError:
|
||||
# python 3
|
||||
try:
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox as tkmessagebox
|
||||
except ImportError:
|
||||
logging.warning("failed to import tkinter - bumblebee popups won't work!")
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.text)
|
||||
)
|
||||
|
||||
self._confirm = True
|
||||
if self.parameter("confirm", "true") == "false":
|
||||
self._confirm = False
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.popup)
|
||||
|
||||
def update(self, widgets):
|
||||
pass
|
||||
|
||||
def text(self, widget):
|
||||
return ""
|
||||
|
||||
def _on_command(self, header, text, command):
|
||||
do_it = True
|
||||
if self._confirm:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
root.focus_set()
|
||||
|
||||
do_it = tkmessagebox.askyesno(header, text)
|
||||
root.destroy()
|
||||
|
||||
if do_it:
|
||||
bumblebee.util.execute(command)
|
||||
|
||||
|
||||
def popup(self, widget):
|
||||
menu = bumblebee.popup_v2.PopupMenu()
|
||||
reboot_cmd = self.parameter("reboot", "reboot")
|
||||
shutdown_cmd = self.parameter("shutdown", "shutdown -h now")
|
||||
logout_cmd = self.parameter("logout", "i3exit logout")
|
||||
switch_user_cmd = self.parameter("switch_user", "i3exit switch_user")
|
||||
lock_cmd = self.parameter("lock", "i3exit lock")
|
||||
suspend_cmd = self.parameter("suspend", "i3exit suspend")
|
||||
hibernate_cmd = self.parameter("hibernate", "i3exit hibernate")
|
||||
|
||||
menu.add_menuitem("shutdown", callback=functools.partial(self._on_command, "Shutdown", "Shutdown?", shutdown_cmd))
|
||||
menu.add_menuitem("reboot", callback=functools.partial(self._on_command, "Reboot", "Reboot?", reboot_cmd))
|
||||
menu.add_menuitem("log out", callback=functools.partial(self._on_command, "Log out", "Log out?", "i3exit logout"))
|
||||
# don't ask for these
|
||||
menu.add_menuitem("switch user", callback=functools.partial(bumblebee.util.execute, switch_user_cmd))
|
||||
menu.add_menuitem("lock", callback=functools.partial(bumblebee.util.execute, lock_cmd))
|
||||
menu.add_menuitem("suspend", callback=functools.partial(bumblebee.util.execute, suspend_cmd))
|
||||
menu.add_menuitem("hibernate", callback=functools.partial(bumblebee.util.execute, hibernate_cmd))
|
||||
|
||||
menu.show(widget)
|
||||
|
||||
def state(self, widget):
|
||||
return []
|
|
@ -1,42 +0,0 @@
|
|||
"""Displays the number of pending tasks in TaskWarrior.
|
||||
|
||||
Requires the following library:
|
||||
* taskw
|
||||
|
||||
Parameters:
|
||||
* taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc)
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
try:
|
||||
from taskw import TaskWarrior
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
"""TaskWarrior module."""
|
||||
|
||||
def __init__(self, engine, config):
|
||||
"""Initialize taskwarrior module."""
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(
|
||||
full_text=self.output))
|
||||
self._pending_tasks_count = "0"
|
||||
|
||||
def update(self, widgets):
|
||||
"""Return a string with the number of pending tasks from TaskWarrior."""
|
||||
try:
|
||||
taskrc = self.parameter("taskrc", "~/.taskrc")
|
||||
w = TaskWarrior(config_filename=taskrc)
|
||||
pending_tasks = w.filter_tasks({'status': 'pending'})
|
||||
self._pending_tasks_count = str(len(pending_tasks))
|
||||
except:
|
||||
self._pending_tasks_count = 'Error'
|
||||
|
||||
def output(self, _):
|
||||
"""Format the task counter to output in bumblebee."""
|
||||
return "{}".format(self._pending_tasks_count)
|
|
@ -1,14 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Test module
|
||||
"""
|
||||
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text="test")
|
||||
)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,85 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays focused i3 window title.
|
||||
|
||||
Requirements:
|
||||
* i3ipc
|
||||
|
||||
Parameters:
|
||||
* title.max : Maximum character length for title before truncating. Defaults to 64.
|
||||
* title.placeholder : Placeholder text to be placed if title was truncated. Defaults to "...".
|
||||
* title.scroll : Boolean flag for scrolling title. Defaults to False
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
try:
|
||||
import i3ipc
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from bumblebee.output import scrollable
|
||||
|
||||
_no_title = "n/a"
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
"""Window title module."""
|
||||
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(
|
||||
engine,
|
||||
config
|
||||
)
|
||||
|
||||
# parsing of parameters
|
||||
self._scroll = bumblebee.util.asbool(self.parameter("scroll", False))
|
||||
self._max = int(self.parameter("max", 64))
|
||||
self._placeholder = self.parameter("placeholder", "...")
|
||||
|
||||
# set output of the module
|
||||
self.widgets(bumblebee.output.Widget(full_text=
|
||||
self._scrolling_focused_title if self._scroll else self._focused_title))
|
||||
|
||||
# create a connection with i3ipc
|
||||
try:
|
||||
self._i3 = i3ipc.Connection()
|
||||
# event is called both on focus change and title change
|
||||
self._i3.on("window", lambda _p_i3, _p_e: self._pollTitle())
|
||||
# begin listening for events
|
||||
threading.Thread(target=self._i3.main).start()
|
||||
except:
|
||||
pass
|
||||
|
||||
# initialize the first title
|
||||
self._pollTitle()
|
||||
|
||||
def _focused_title(self, widget):
|
||||
return self._title
|
||||
|
||||
@scrollable
|
||||
def _scrolling_focused_title(self, widget):
|
||||
return self._full_title
|
||||
|
||||
def _pollTitle(self):
|
||||
"""Updating current title."""
|
||||
try:
|
||||
self._full_title = self._i3.get_tree().find_focused().name
|
||||
except:
|
||||
self._full_title = _no_title
|
||||
if self._full_title is None:
|
||||
self._full_title = _no_title
|
||||
|
||||
if not self._scroll:
|
||||
# cut the text if it is too long
|
||||
if len(self._full_title) > self._max:
|
||||
self._title = self._full_title[0:self._max - len(self._placeholder)]
|
||||
self._title = "{}{}".format(self._title, self._placeholder)
|
||||
else:
|
||||
self._title = self._full_title
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,49 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the number of todo items from a text file
|
||||
|
||||
Requires the following executable:
|
||||
* xdg-open
|
||||
|
||||
Parameters:
|
||||
* todo.file: File to read TODOs from (defaults to ~/Documents/todo.txt)
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import os.path
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
|
||||
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.output)
|
||||
)
|
||||
self._doc = os.path.expanduser(self.parameter("file", "~/Documents/todo.txt"))
|
||||
self._todos = self.count_items()
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd="xdg-open {0}".format(self._doc))
|
||||
|
||||
|
||||
def output(self, widget):
|
||||
self._todos = self.count_items()
|
||||
return str(self._todos)
|
||||
|
||||
|
||||
def state(self, widgets):
|
||||
if self._todos == 0:
|
||||
return "empty"
|
||||
return "items"
|
||||
|
||||
|
||||
def count_items(self):
|
||||
try:
|
||||
i = -1
|
||||
with open(self._doc) as f:
|
||||
for i, l in enumerate(f):
|
||||
pass
|
||||
return i+1
|
||||
except Exception:
|
||||
return 0
|
|
@ -1,39 +0,0 @@
|
|||
#pylint: disable=C0111,R0903
|
||||
|
||||
"""Toggle twmn notifications."""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text="")
|
||||
)
|
||||
self._paused = False
|
||||
# Make sure that twmn is currently not paused
|
||||
try:
|
||||
bumblebee.util.execute("killall -SIGUSR2 twmnd")
|
||||
except:
|
||||
pass
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.toggle_status
|
||||
)
|
||||
|
||||
def toggle_status(self, event):
|
||||
self._paused = not self._paused
|
||||
|
||||
try:
|
||||
if self._paused:
|
||||
bumblebee.util.execute("systemctl --user start twmnd")
|
||||
else:
|
||||
bumblebee.util.execute("systemctl --user stop twmnd")
|
||||
except:
|
||||
self._paused = not self._paused # toggling failed
|
||||
|
||||
def state(self, widget):
|
||||
if self._paused:
|
||||
return ["muted"]
|
||||
return ["unmuted"]
|
|
@ -1,30 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the system uptime."""
|
||||
|
||||
# Use absolute_import because there's already a datatime module
|
||||
# in the same directory
|
||||
from __future__ import absolute_import
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.output)
|
||||
)
|
||||
self._uptime = ""
|
||||
|
||||
def output(self, _):
|
||||
return "{}".format(self._uptime)
|
||||
|
||||
def update(self, widgets):
|
||||
with open('/proc/uptime', 'r') as f:
|
||||
uptime_seconds = int(float(f.readline().split()[0]))
|
||||
self._uptime = timedelta(seconds = uptime_seconds)
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,81 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Copy passwords from a password store into the clipboard (currently supports only "pass")
|
||||
|
||||
Many thanks to [@bbernhard](https://github.com/bbernhard) for the idea!
|
||||
|
||||
Parameters:
|
||||
* vault.duration: Duration until password is cleared from clipboard (defaults to 30)
|
||||
* vault.location: Location of the password store (defaults to ~/.password-store)
|
||||
* vault.offx: x-axis offset of popup menu (defaults to 0)
|
||||
* vault.offy: y-axis offset of popup menu (defaults to 0)
|
||||
"""
|
||||
|
||||
|
||||
# TODO:
|
||||
# - support multiple backends by abstracting the menu structure into a tree
|
||||
# - build the menu and the actions based on that abstracted tree
|
||||
#
|
||||
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import bumblebee.util
|
||||
import bumblebee.popup_v2
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
def build_menu(parent, current_directory, callback):
|
||||
with os.scandir(current_directory) as it:
|
||||
for entry in it:
|
||||
if entry.name.startswith("."): continue
|
||||
if entry.is_file():
|
||||
name = entry.name[:entry.name.rfind(".")]
|
||||
parent.add_menuitem(name, callback=lambda : callback(os.path.join(current_directory, name)))
|
||||
|
||||
else:
|
||||
submenu = bumblebee.popup_v2.PopupMenu(parent, leave=False)
|
||||
build_menu(submenu, os.path.join(current_directory, entry.name), callback)
|
||||
parent.add_cascade(entry.name, submenu)
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.text)
|
||||
)
|
||||
self._duration = int(self.parameter("duration", 30))
|
||||
self._offx = int(self.parameter("offx", 0))
|
||||
self._offy = int(self.parameter("offy", 0))
|
||||
self._path = os.path.expanduser(self.parameter("location", "~/.password-store/"))
|
||||
self._reset()
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.popup)
|
||||
|
||||
def popup(self, widget):
|
||||
menu = bumblebee.popup_v2.PopupMenu(leave=False)
|
||||
|
||||
build_menu(menu, self._path, self._callback)
|
||||
menu.show(widget, offset_x=self._offx, offset_y=self._offy)
|
||||
|
||||
def _reset(self):
|
||||
self._timer = None
|
||||
self._text = str(self.parameter("text", "<click-for-password>"))
|
||||
|
||||
def _callback(self, secret_name):
|
||||
secret_name = secret_name.replace(self._path, "") # remove common path
|
||||
if self._timer:
|
||||
self._timer.cancel()
|
||||
# bumblebee.util.execute hangs for some reason
|
||||
os.system("PASSWORD_STORE_CLIP_TIME={} pass -c {} > /dev/null 2>&1".format(self._duration, secret_name))
|
||||
self._timer = threading.Timer(self._duration, self._reset)
|
||||
self._timer.start()
|
||||
self._start = int(time.time())
|
||||
self._text = secret_name
|
||||
|
||||
def text(self, widget):
|
||||
if self._timer:
|
||||
return "{} ({}s)".format(self._text, self._duration - (int(time.time()) - self._start))
|
||||
return self._text
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,102 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
""" Displays the VPN profile that is currently in use.
|
||||
|
||||
Left click opens a popup menu that lists all available VPN profiles and allows to establish
|
||||
a VPN connection using that profile.
|
||||
|
||||
Prerequisites:
|
||||
* tk python library (usually python-tk or python3-tk, depending on your distribution)
|
||||
* nmcli needs to be installed and configured properly.
|
||||
To quickly test, whether nmcli is working correctly, type "nmcli -g NAME,TYPE,DEVICE con" which
|
||||
lists all the connection profiles that are configured. Make sure that your VPN profile is in that list!
|
||||
|
||||
e.g: to import a openvpn profile via nmcli:
|
||||
sudo nmcli connection import type openvpn file </path/to/your/openvpn/profile.ovpn>
|
||||
"""
|
||||
|
||||
import logging
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.popup_v2
|
||||
import functools
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.vpn_status)
|
||||
)
|
||||
|
||||
self._connected_vpn_profile = None
|
||||
self._selected_vpn_profile = None
|
||||
|
||||
res = bumblebee.util.execute("nmcli -g NAME,TYPE c")
|
||||
lines = res.splitlines()
|
||||
|
||||
self._vpn_profiles = []
|
||||
for line in lines:
|
||||
info = line.split(':')
|
||||
try:
|
||||
if self._isvpn(info[1]):
|
||||
self._vpn_profiles.append(info[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.popup)
|
||||
|
||||
def _isvpn(self, connection_type):
|
||||
return connection_type in ["vpn", "wireguard"]
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
res = bumblebee.util.execute("nmcli -g NAME,TYPE,DEVICE con")
|
||||
lines = res.splitlines()
|
||||
self._connected_vpn_profile = None
|
||||
for line in lines:
|
||||
info = line.split(':')
|
||||
if self._isvpn(info[1]) and info[2] != "":
|
||||
self._connected_vpn_profile = info[0]
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("Couldn't get VPN status")
|
||||
self._connected_vpn_profile = None
|
||||
|
||||
def vpn_status(self, widget):
|
||||
if self._connected_vpn_profile is None:
|
||||
return "off"
|
||||
return self._connected_vpn_profile
|
||||
|
||||
def _on_vpn_disconnect(self):
|
||||
try:
|
||||
bumblebee.util.execute("nmcli c down \"{vpn}\""
|
||||
.format(vpn=self._connected_vpn_profile))
|
||||
self._connected_vpn_profile = None
|
||||
except Exception as e:
|
||||
logging.exception("Couldn't disconnect VPN connection")
|
||||
|
||||
def _on_vpn_connect(self, name):
|
||||
self._selected_vpn_profile = name
|
||||
|
||||
try:
|
||||
bumblebee.util.execute("nmcli c up \"{vpn}\""
|
||||
.format(vpn=self._selected_vpn_profile))
|
||||
self._connected_vpn_profile = name
|
||||
except Exception as e:
|
||||
logging.exception("Couldn't establish VPN connection")
|
||||
self._connected_vpn_profile = None
|
||||
|
||||
def popup(self, widget):
|
||||
menu = bumblebee.popup_v2.PopupMenu()
|
||||
|
||||
if self._connected_vpn_profile is not None:
|
||||
menu.add_menuitem("Disconnect", callback=self._on_vpn_disconnect)
|
||||
for vpn_profile in self._vpn_profiles:
|
||||
if self._connected_vpn_profile is not None and self._connected_vpn_profile == vpn_profile:
|
||||
continue
|
||||
menu.add_menuitem(vpn_profile, callback=functools.partial(self._on_vpn_connect, vpn_profile))
|
||||
menu.show(widget)
|
||||
|
||||
def state(self, widget):
|
||||
return []
|
|
@ -1,57 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the status of watson (time-tracking tool)
|
||||
|
||||
Requires the following executable:
|
||||
* watson
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import bumblebee.util
|
||||
import bumblebee.popup_v2
|
||||
|
||||
import logging
|
||||
import re
|
||||
import functools
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.text))
|
||||
self._tracking = False
|
||||
self._project = ""
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd=self.toggle)
|
||||
|
||||
def toggle(self, widget):
|
||||
self._project = "hit"
|
||||
if self._tracking:
|
||||
bumblebee.util.execute("watson stop")
|
||||
else:
|
||||
bumblebee.util.execute("watson restart")
|
||||
self._tracking = not self._tracking
|
||||
|
||||
def text(self, widget):
|
||||
if self._tracking:
|
||||
return self._project
|
||||
else:
|
||||
return "Paused"
|
||||
|
||||
def update(self, widgets):
|
||||
output = bumblebee.util.execute("watson status")
|
||||
if re.match('No project started', output):
|
||||
self._tracking = False
|
||||
return
|
||||
|
||||
self._tracking = True
|
||||
m = re.search(r'Project (.+) started', output)
|
||||
self._project = m.group(1)
|
||||
|
||||
#
|
||||
def state(self, widget):
|
||||
return "on" if self._tracking else "off"
|
||||
# return [widget.get("status", None), widget.get("period", None)]
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,138 +0,0 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the temperature on the current location based on the ip
|
||||
|
||||
Requires the following python packages:
|
||||
* requests
|
||||
|
||||
Parameters:
|
||||
* weather.location: Set location, defaults to 'auto' for getting location from http://ipinfo.io
|
||||
If set to a comma-separated list, left-click and right-click can be used to rotate the locations.
|
||||
Locations should be city names or city ids.
|
||||
* weather.unit: metric (default), kelvin, imperial
|
||||
* weather.showcity: If set to true, show location information, otherwise hide it (defaults to true)
|
||||
* weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false)
|
||||
* weather.apikey: API key from http://api.openweathermap.org
|
||||
"""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
import re
|
||||
|
||||
try:
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.output)
|
||||
)
|
||||
self._temperature = 0
|
||||
self._apikey = self.parameter("apikey", "af7bfe22287c652d032a3064ffa44088")
|
||||
self._location = self.parameter("location", "auto")
|
||||
if "," in self._location:
|
||||
self._location = self._location.split(",")
|
||||
else:
|
||||
self._location = [self._location]
|
||||
self._index = 0
|
||||
self._showcity = bumblebee.util.asbool(self.parameter("showcity", True))
|
||||
self._showminmax = bumblebee.util.asbool(self.parameter("showminmax", False))
|
||||
self._unit = self.parameter("unit", "metric")
|
||||
self._valid = False
|
||||
self.interval_factor(60)
|
||||
self.interval(15)
|
||||
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._next_location)
|
||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self._prev_location)
|
||||
|
||||
def _next_location(self, event):
|
||||
self._index = 0 if self._index >= len(self._location) - 1 else self._index + 1
|
||||
self.update(self.widgets())
|
||||
|
||||
def _prev_location(self, event):
|
||||
self._index = len(self._location)-1 if self._index <= 0 else self._index - 1
|
||||
self.update(self.widgets())
|
||||
|
||||
def _unit_suffix(self):
|
||||
if self._unit == "metric":
|
||||
return "C"
|
||||
if self._unit == "kelvin":
|
||||
return "K"
|
||||
if self._unit == "imperial":
|
||||
return "F"
|
||||
return ""
|
||||
|
||||
def temperature(self):
|
||||
return u"{}°{}".format(self._temperature, self._unit_suffix())
|
||||
|
||||
def tempmin(self):
|
||||
return u"{}°{}".format(self._tempmin, self._unit_suffix())
|
||||
|
||||
def tempmax(self):
|
||||
return u"{}°{}".format(self._tempmax, self._unit_suffix())
|
||||
|
||||
def city(self):
|
||||
city = re.sub('[_-]', ' ', self._city)
|
||||
return u"{} ".format(city)
|
||||
|
||||
def output(self, widget):
|
||||
if not self._valid:
|
||||
return u"?"
|
||||
if self._showminmax:
|
||||
self._showcity=False
|
||||
return self.city() + self.temperature() + " Hi:" + self.tempmax() + " Lo:" + self.tempmin()
|
||||
elif self._showcity:
|
||||
return self.city() + self.temperature()
|
||||
else:
|
||||
return self.temperature()
|
||||
|
||||
def state(self, widget):
|
||||
if self._valid:
|
||||
if "thunderstorm" in self._weather:
|
||||
return ['thunder']
|
||||
elif "drizzle" in self._weather:
|
||||
return ['rain']
|
||||
elif "rain" in self._weather:
|
||||
return ['rain']
|
||||
elif "snow" in self._weather:
|
||||
return ['snow']
|
||||
elif "sleet" in self._weather:
|
||||
return ['sleet']
|
||||
elif "clear" in self._weather:
|
||||
return ['clear']
|
||||
elif "cloud" in self._weather:
|
||||
return ['clouds']
|
||||
else:
|
||||
return []
|
||||
|
||||
return []
|
||||
|
||||
def update(self, widgets):
|
||||
try:
|
||||
weather_url = "http://api.openweathermap.org/data/2.5/weather?appid={}".format(self._apikey)
|
||||
weather_url = "{}&units={}".format(weather_url, self._unit)
|
||||
if self._location[self._index] == "auto":
|
||||
location_url = "http://ipinfo.io/json"
|
||||
location = requests.get(location_url).json()
|
||||
coord = location["loc"].split(",")
|
||||
weather_url = "{url}&lat={lat}&lon={lon}".format(url=weather_url, lat=coord[0], lon=coord[1])
|
||||
elif self._location[self._index].isdigit():
|
||||
weather_url = "{url}&id={id}".format(url=weather_url, id=self._location[self._index])
|
||||
else:
|
||||
weather_url = "{url}&q={city}".format(url=weather_url, city=self._location[self._index])
|
||||
weather = requests.get(weather_url).json()
|
||||
self._city = weather['name']
|
||||
self._temperature = int(weather['main']['temp'])
|
||||
self._tempmin = int(weather['main']['temp_min'])
|
||||
self._tempmax = int(weather['main']['temp_max'])
|
||||
self._weather = weather['weather'][0]['main'].lower()
|
||||
self._valid = True
|
||||
except Exception:
|
||||
self._valid = False
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,17 +0,0 @@
|
|||
#pylint: disable=C0111,R0903
|
||||
|
||||
"""Opens a random xkcd comic in the browser."""
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text="xkcd")
|
||||
)
|
||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
||||
cmd="xdg-open https://c.xkcd.com/random/comic/"
|
||||
)
|
|
@ -1,118 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Shows a widget for each connected screen and allows the user to enable/disable screens.
|
||||
|
||||
Parameters:
|
||||
* xrandr.overwrite_i3config: If set to 'true', this module assembles a new i3 config
|
||||
every time a screen is enabled or disabled by taking the file "~/.config/i3/config.template"
|
||||
and appending a file "~/.config/i3/config.<screen name>" for every screen.
|
||||
* xrandr.autoupdate: If set to 'false', does *not* invoke xrandr automatically. Instead, the
|
||||
module will only refresh when displays are enabled or disabled (defaults to true)
|
||||
|
||||
Requires the following python module:
|
||||
* (optional) i3 - if present, the need for updating the widget list is auto-detected
|
||||
|
||||
Requires the following executable:
|
||||
* xrandr
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import bumblebee.util
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
try:
|
||||
import i3
|
||||
except:
|
||||
pass
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
widgets = []
|
||||
self._engine = engine
|
||||
super(Module, self).__init__(engine, config, widgets)
|
||||
self._autoupdate = bumblebee.util.asbool(self.parameter("autoupdate", True))
|
||||
self._needs_update = True
|
||||
|
||||
try:
|
||||
i3.Subscription(self._output_update, "output")
|
||||
except:
|
||||
pass
|
||||
|
||||
def _output_update(self, event, data, _):
|
||||
self._needs_update = True
|
||||
|
||||
def update_widgets(self, widgets):
|
||||
new_widgets = []
|
||||
|
||||
if self._autoupdate == False and self._needs_update == False:
|
||||
return
|
||||
|
||||
self._needs_update = False
|
||||
|
||||
for line in bumblebee.util.execute("xrandr -q").split("\n"):
|
||||
if not " connected" in line:
|
||||
continue
|
||||
display = line.split(" ", 2)[0]
|
||||
m = re.search(r'\d+x\d+\+(\d+)\+\d+', line)
|
||||
|
||||
widget = self.widget(display)
|
||||
if not widget:
|
||||
widget = bumblebee.output.Widget(full_text=display, name=display)
|
||||
self._engine.input.register_callback(widget, button=1, cmd=self._toggle)
|
||||
self._engine.input.register_callback(widget, button=3, cmd=self._toggle)
|
||||
new_widgets.append(widget)
|
||||
widget.set("state", "on" if m else "off")
|
||||
widget.set("pos", int(m.group(1)) if m else sys.maxsize)
|
||||
|
||||
while len(widgets) > 0:
|
||||
del widgets[0]
|
||||
for widget in new_widgets:
|
||||
widgets.append(widget)
|
||||
|
||||
if self._autoupdate == False:
|
||||
widget = bumblebee.output.Widget(full_text="")
|
||||
widget.set("state", "refresh")
|
||||
self._engine.input.register_callback(widget, button=1, cmd=self._refresh)
|
||||
widgets.append(widget)
|
||||
|
||||
def update(self, widgets):
|
||||
self.update_widgets(widgets)
|
||||
|
||||
def state(self, widget):
|
||||
return widget.get("state", "off")
|
||||
|
||||
def _refresh(self, event):
|
||||
self._needs_update = True
|
||||
|
||||
def _toggle(self, event):
|
||||
self._needs_update = True
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
if bumblebee.util.asbool(self.parameter("overwrite_i3config", False)) == True:
|
||||
toggle_cmd = "{}/../../bin/toggle-display.sh".format(path)
|
||||
else:
|
||||
toggle_cmd = "xrandr"
|
||||
|
||||
widget = self.widget_by_id(event["instance"])
|
||||
|
||||
if widget.get("state") == "on":
|
||||
bumblebee.util.execute("{} --output {} --off".format(toggle_cmd, widget.name))
|
||||
else:
|
||||
first_neighbor = next((widget for widget in self.widgets() if widget.get("state") == "on"), None)
|
||||
last_neighbor = next((widget for widget in reversed(self.widgets()) if widget.get("state") == "on"), None)
|
||||
|
||||
neighbor = first_neighbor if event["button"] == bumblebee.input.LEFT_MOUSE else last_neighbor
|
||||
|
||||
if neighbor is None:
|
||||
bumblebee.util.execute("{} --output {} --auto".format(toggle_cmd, widget.name))
|
||||
else:
|
||||
bumblebee.util.execute("{} --output {} --auto --{}-of {}".format(toggle_cmd, widget.name,
|
||||
"left" if event.get("button") == bumblebee.input.LEFT_MOUSE else "right",
|
||||
neighbor.name))
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,33 +0,0 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Shows yubikey information
|
||||
|
||||
Requires: https://github.com/Yubico/python-yubico
|
||||
|
||||
The output indicates that a YubiKey is not connected or it displays
|
||||
the corresponding serial number.
|
||||
|
||||
"""
|
||||
|
||||
import yubico
|
||||
|
||||
import bumblebee.input
|
||||
import bumblebee.output
|
||||
import bumblebee.engine
|
||||
|
||||
class Module(bumblebee.engine.Module):
|
||||
def __init__(self, engine, config):
|
||||
super(Module, self).__init__(engine, config,
|
||||
bumblebee.output.Widget(full_text=self.keystate))
|
||||
self._keystate = "No YubiKey"
|
||||
|
||||
def keystate(self, widget):
|
||||
return self._keystate
|
||||
|
||||
def update(self, widget):
|
||||
try:
|
||||
self._keystate = "YubiKey: " + str(yubico.find_yubikey(debug=False).serial())
|
||||
except yubico.yubico_exception.YubicoError:
|
||||
self._keystate = "No YubiKey"
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,527 +0,0 @@
|
|||
# pylint: disable=R0201
|
||||
|
||||
"""Output classes"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
import logging
|
||||
import xml.etree.ElementTree
|
||||
|
||||
import bumblebee.store
|
||||
import bumblebee.util
|
||||
|
||||
MAX_PERCENTS = 100.
|
||||
CHARS = 8
|
||||
HBARS = [
|
||||
u"\u2581",
|
||||
u"\u2582",
|
||||
u"\u2583",
|
||||
u"\u2584",
|
||||
u"\u2585",
|
||||
u"\u2586",
|
||||
u"\u2587",
|
||||
u"\u2588"]
|
||||
VBARS = [
|
||||
u"\u258f",
|
||||
u"\u258e",
|
||||
u"\u258d",
|
||||
u"\u258c",
|
||||
u"\u258b",
|
||||
u"\u258a",
|
||||
u"\u2589",
|
||||
u"\u2588"]
|
||||
BRAILLE = {
|
||||
(0, 0): u" ",
|
||||
(1, 0): u"\u2840",
|
||||
(2, 0): u"\u2844",
|
||||
(3, 0): u"\u2846",
|
||||
(4, 0): u"\u2847",
|
||||
(0, 1): u"\u2880",
|
||||
(0, 2): u"\u28a0",
|
||||
(0, 3): u"\u28b0",
|
||||
(0, 4): u"\u28b8",
|
||||
(1, 1): u"\u28c0",
|
||||
(2, 1): u"\u28c4",
|
||||
(3, 1): u"\u28c6",
|
||||
(4, 1): u"\u28c7",
|
||||
(1, 2): u"\u28e0",
|
||||
(2, 2): u"\u28e4",
|
||||
(3, 2): u"\u28e6",
|
||||
(4, 2): u"\u28e7",
|
||||
(1, 3): u"\u28f0",
|
||||
(2, 3): u"\u28f4",
|
||||
(3, 3): u"\u28f6",
|
||||
(4, 3): u"\u28f7",
|
||||
(1, 4): u"\u28f8",
|
||||
(2, 4): u"\u28fc",
|
||||
(3, 4): u"\u28fe",
|
||||
(4, 4): u"\u28ff"
|
||||
}
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def scrollable(func):
|
||||
def wrapper(module, widget):
|
||||
text = func(module, widget)
|
||||
if not text:
|
||||
return text
|
||||
width = widget.get("theme.width", int(module.parameter("width", 30)))
|
||||
if bumblebee.util.asbool(module.parameter("scrolling.makewide", "true")):
|
||||
widget.set("theme.minwidth", "A"*width)
|
||||
if width < 0:
|
||||
return text
|
||||
if len(text) <= width:
|
||||
# do alignment
|
||||
align = module.parameter("theme.align", "left")
|
||||
if align == "right":
|
||||
text = "{:>{}}".format(text, width)
|
||||
if align == "center":
|
||||
text = "{:^{}}".format(text, width)
|
||||
return text
|
||||
# we need to shorten
|
||||
|
||||
try:
|
||||
bounce = int(module.parameter("scrolling.bounce", 1))
|
||||
except ValueError:
|
||||
bounce = 1
|
||||
try:
|
||||
scroll_speed = int(module.parameter("scrolling.speed", 1))
|
||||
except ValueError:
|
||||
scroll_speed = 1
|
||||
start = widget.get("scrolling.start", -1)
|
||||
direction = widget.get("scrolling.direction", "right")
|
||||
start += scroll_speed if direction == "right" else -(scroll_speed)
|
||||
|
||||
if width + start > len(text) + (scroll_speed -1):
|
||||
if bounce:
|
||||
widget.set("scrolling.direction", "left")
|
||||
else:
|
||||
start = 0
|
||||
elif start <= 0:
|
||||
if bounce:
|
||||
widget.set("scrolling.direction", "right")
|
||||
else:
|
||||
start = len(text)
|
||||
widget.set("scrolling.start", start)
|
||||
text = text[start:width+start]
|
||||
|
||||
return text
|
||||
return wrapper
|
||||
|
||||
|
||||
class Bar(object):
|
||||
"""superclass"""
|
||||
bars = None
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Args:
|
||||
|
||||
value (float): value between 0. and 100. meaning percents
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
|
||||
class HBar(Bar):
|
||||
"""horizontal bar (1 char)"""
|
||||
bars = HBARS
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Args:
|
||||
|
||||
value (float): value between 0. and 100. meaning percents
|
||||
"""
|
||||
super(HBar, self).__init__(value)
|
||||
self.step = MAX_PERCENTS / CHARS
|
||||
|
||||
def get_char(self):
|
||||
"""
|
||||
Decide which char to draw
|
||||
|
||||
Return: str
|
||||
"""
|
||||
for i in range(CHARS):
|
||||
left = i * self.step
|
||||
right = (i + 1) * self.step
|
||||
if left <= self.value < right:
|
||||
return self.bars[i]
|
||||
return self.bars[-1]
|
||||
|
||||
|
||||
def hbar(value):
|
||||
"""wrapper function"""
|
||||
return HBar(value).get_char()
|
||||
|
||||
|
||||
class VBar(Bar):
|
||||
"""vertical bar (can be more than 1 char)"""
|
||||
bars = VBARS
|
||||
|
||||
def __init__(self, value, width=1):
|
||||
"""
|
||||
Args:
|
||||
|
||||
value (float): value between 0. and 100. meaning percents
|
||||
|
||||
width (int): width
|
||||
"""
|
||||
super(VBar, self).__init__(value)
|
||||
self.step = MAX_PERCENTS / (CHARS * width)
|
||||
self.width = width
|
||||
|
||||
def get_chars(self):
|
||||
"""
|
||||
Decide which char to draw
|
||||
|
||||
Return: str
|
||||
"""
|
||||
if self.value == 100:
|
||||
return self.bars[-1] * self.width
|
||||
if self.width == 1:
|
||||
for i in range(CHARS):
|
||||
left = i * self.step
|
||||
right = (i + 1) * self.step
|
||||
if left <= self.value < right:
|
||||
return self.bars[i]
|
||||
else:
|
||||
full_parts = int(self.value // (self.step * CHARS))
|
||||
remainder = self.value - full_parts * self.step * CHARS
|
||||
empty_parts = self.width - full_parts
|
||||
if remainder >= 0:
|
||||
empty_parts -= 1
|
||||
part_vbar = VBar(remainder * self.width) # scale to width
|
||||
chars = self.bars[-1] * full_parts
|
||||
chars += part_vbar.get_chars()
|
||||
chars += " " * empty_parts
|
||||
return chars
|
||||
|
||||
|
||||
def vbar(value, width):
|
||||
"""wrapper function"""
|
||||
return VBar(value, width).get_chars()
|
||||
|
||||
|
||||
class BrailleGraph(object):
|
||||
"""
|
||||
graph using Braille chars
|
||||
scaled to passed values
|
||||
"""
|
||||
def __init__(self, values):
|
||||
"""
|
||||
Args:
|
||||
|
||||
values (list): list of values
|
||||
"""
|
||||
self.values = values
|
||||
# length of values list must be even
|
||||
# because one Braille char displays two values
|
||||
if len(self.values) % 2 == 1:
|
||||
self.values.append(0)
|
||||
self.steps = self.get_steps()
|
||||
self.parts = [tuple(self.steps[i:i+2])
|
||||
for i in range(len(self.steps))[::2]]
|
||||
|
||||
@staticmethod
|
||||
def get_height(value, unit):
|
||||
"""
|
||||
Compute height of a value relative to unit
|
||||
|
||||
Args:
|
||||
|
||||
value (number): value
|
||||
|
||||
unit (number): unit
|
||||
"""
|
||||
if value < unit / 10.:
|
||||
return 0
|
||||
elif value <= unit:
|
||||
return 1
|
||||
elif value <= unit * 2:
|
||||
return 2
|
||||
elif value <= unit * 3:
|
||||
return 3
|
||||
else:
|
||||
return 4
|
||||
|
||||
def get_steps(self):
|
||||
"""
|
||||
Convert the list of values to a list of steps
|
||||
|
||||
Return: list
|
||||
"""
|
||||
maxval = max(self.values)
|
||||
unit = maxval / 4.
|
||||
if unit == 0:
|
||||
return [0] * len(self.values)
|
||||
stepslist = []
|
||||
for value in self.values:
|
||||
stepslist.append(self.get_height(value, unit))
|
||||
return stepslist
|
||||
|
||||
def get_chars(self):
|
||||
"""
|
||||
Decide which chars to draw
|
||||
|
||||
Return: str
|
||||
"""
|
||||
chars = []
|
||||
for part in self.parts:
|
||||
chars.append(BRAILLE[part])
|
||||
return "".join(chars)
|
||||
|
||||
|
||||
def bgraph(values):
|
||||
"""wrapper function"""
|
||||
return BrailleGraph(values).get_chars()
|
||||
|
||||
|
||||
class Widget(bumblebee.store.Store):
|
||||
"""Represents a single visible block in the status bar"""
|
||||
def __init__(self, full_text="", name=""):
|
||||
super(Widget, self).__init__()
|
||||
self._full_text = full_text
|
||||
self.module = None
|
||||
self._module = None
|
||||
self._minimized = False
|
||||
self.name = name
|
||||
self.id = str(uuid.uuid4())
|
||||
|
||||
def get_module(self):
|
||||
return self._module
|
||||
|
||||
def toggle_minimize(self):
|
||||
self._minimized = not self._minimized
|
||||
|
||||
def link_module(self, module):
|
||||
"""Set the module that spawned this widget
|
||||
|
||||
This is done outside the constructor to avoid having to
|
||||
pass in the module name in every concrete module implementation"""
|
||||
self.module = module.name
|
||||
self._module = module
|
||||
|
||||
def cls(self):
|
||||
if not self._module:
|
||||
return None
|
||||
return self._module.__module__.replace("bumblebee.modules.", "")
|
||||
|
||||
def state(self):
|
||||
"""Return the widget's state"""
|
||||
if self._module and hasattr(self._module, "state"):
|
||||
states = self._module.state(self)
|
||||
if not isinstance(states, list):
|
||||
return [states]
|
||||
return states
|
||||
return []
|
||||
|
||||
def full_text(self, value=None):
|
||||
"""Set or retrieve the full text to display in the widget"""
|
||||
if value:
|
||||
self._full_text = value
|
||||
else:
|
||||
if self._minimized:
|
||||
return u"\u2026"
|
||||
if callable(self._full_text):
|
||||
return self._full_text(self)
|
||||
else:
|
||||
return self._full_text
|
||||
|
||||
|
||||
class WidgetDrawer(object):
|
||||
"""
|
||||
Wrapper for I3BarOutput.draw(),
|
||||
because that function is getting too big
|
||||
"""
|
||||
def __init__(self, theme, config=None):
|
||||
"""
|
||||
Keep the same signature as I3BarOutput.__init__()
|
||||
"""
|
||||
self._theme = theme
|
||||
self._config = config
|
||||
self._widgets = []
|
||||
self._markup = None
|
||||
self._full_text = None
|
||||
self._prefix = None
|
||||
self._prefix_fg = None
|
||||
self._prefix_bg = None
|
||||
self._iconmarkup = None
|
||||
self._suffix = None
|
||||
|
||||
def add_separator(self, widget, separator):
|
||||
"""Add separator (if theme has one)"""
|
||||
if separator:
|
||||
self._widgets.append({
|
||||
u"full_text": separator,
|
||||
"separator": False,
|
||||
"color": self._theme.separator_fg(widget),
|
||||
"background": self._theme.separator_bg(widget),
|
||||
"separator_block_width": self._theme.separator_block_width(widget),
|
||||
})
|
||||
|
||||
def add_prefix_iconmarkup(self, widget):
|
||||
"""add custom Pango markup for prefix"""
|
||||
element = xml.etree.ElementTree.XML(self._iconmarkup)
|
||||
# if the custom markup has neither 'foreground' or 'fgcolor'
|
||||
# attributes, but theme has prefixfg, merge it
|
||||
if 'foreground' not in element.keys() and 'fgcolor' not in element.keys() and self._prefix_fg is not None:
|
||||
element.set("foreground", self._prefix_fg)
|
||||
# if the custom markup has neither 'background' or 'bgcolor'
|
||||
# attributes, but theme has prefixbg, merge it
|
||||
if 'background' not in element.keys() and 'bgcolor' not in element.keys() and self._prefix_bg is not None:
|
||||
element.set("background", self._prefix_bg)
|
||||
self._prefix = xml.etree.ElementTree.tostring(element).decode("utf-8").format(self._prefix)
|
||||
|
||||
def add_prefix_colors(self, widget):
|
||||
"""add custom theme colors for prefix"""
|
||||
self._prefix = "<span {} {}>{}</span>".format(
|
||||
"foreground='{}'".format(self._prefix_fg) if self._prefix_fg else "",
|
||||
"background='{}'".format(self._prefix_bg) if self._prefix_bg else "",
|
||||
self._prefix
|
||||
)
|
||||
|
||||
def add_prefix(self, widget, padding):
|
||||
"""add prefix to full_text"""
|
||||
self._prefix = self._theme.prefix(widget, padding)
|
||||
|
||||
if self._markup == "pango":
|
||||
# add prefix/suffix colors
|
||||
self._prefix_fg = self._theme.prefix_fg(widget)
|
||||
self._prefix_bg = self._theme.prefix_bg(widget)
|
||||
self._iconmarkup = self._config.iconmarkup()
|
||||
if self._iconmarkup != "none":
|
||||
self.add_prefix_iconmarkup(widget)
|
||||
else:
|
||||
self.add_prefix_colors(widget)
|
||||
|
||||
if self._prefix:
|
||||
self._full_text = u"{}{}".format(self._prefix, self._full_text)
|
||||
return self._prefix
|
||||
|
||||
def add_suffix_iconmarkup(self, widget):
|
||||
"""add custom Pango markup for suffix"""
|
||||
self._suffix = self._iconmarkup.format(self._suffix)
|
||||
|
||||
def add_suffix(self, widget, padding):
|
||||
"""add suffix to full_text"""
|
||||
self._suffix = self._theme.suffix(widget, padding)
|
||||
|
||||
if self._markup == "pango":
|
||||
if self._iconmarkup != "none":
|
||||
self.add_suffix_iconmarkup(widget)
|
||||
|
||||
if self._suffix:
|
||||
self._full_text = u"{}{}".format(self._full_text, self._suffix)
|
||||
return self._suffix
|
||||
|
||||
def escape_amp(self):
|
||||
"""escape & in full_text, because pango requires it"""
|
||||
if self._markup == "pango":
|
||||
self._full_text = self._full_text.replace("&", "&")
|
||||
|
||||
def draw(self, widget, module=None, engine=None):
|
||||
"""
|
||||
Keep the same argument signature as I3BarOutput.draw()
|
||||
Return: list
|
||||
list[0] - optional if the theme has a separator
|
||||
list[1] - JSON text for the widget
|
||||
"""
|
||||
|
||||
if widget.get_module() and widget.get_module().hidden():
|
||||
return []
|
||||
if widget.get_module() and widget.get_module().name in self._config.autohide():
|
||||
if not any(state in widget.state() for state in ["warning", "critical"]):
|
||||
return []
|
||||
|
||||
separator = self._theme.separator(widget)
|
||||
self.add_separator(widget, separator)
|
||||
|
||||
self._markup = "none" if not self._config else self._config.markup()
|
||||
|
||||
self._full_text = widget.full_text()
|
||||
raw = self._full_text
|
||||
|
||||
padding = self._theme.padding(widget)
|
||||
prefix = self.add_prefix(widget, padding)
|
||||
suffix = self.add_suffix(widget, padding)
|
||||
|
||||
width = self._theme.minwidth(widget)
|
||||
|
||||
if width:
|
||||
self._full_text = self._full_text.ljust(len(width) + len(self._prefix) + len(self._suffix))
|
||||
raw = raw.ljust(len(width))
|
||||
|
||||
self.escape_amp()
|
||||
|
||||
self._widgets.append({
|
||||
u"full_text": self._full_text,
|
||||
"color": self._theme.fg(widget),
|
||||
"background": self._theme.bg(widget),
|
||||
"separator_block_width": self._theme.separator_block_width(widget),
|
||||
"separator": self._theme.default_separators(widget),
|
||||
"min_width": None,
|
||||
# "min_width": width + "A"*(len(self._prefix) + len(self._suffix)) if width else None,
|
||||
"align": self._theme.align(widget),
|
||||
"instance": widget.id,
|
||||
"name": module.id,
|
||||
"markup": self._markup,
|
||||
"_raw": raw,
|
||||
"_prefix": prefix,
|
||||
"_suffix": suffix,
|
||||
})
|
||||
return self._widgets
|
||||
|
||||
|
||||
class I3BarOutput(object):
|
||||
"""Manage output according to the i3bar protocol"""
|
||||
def __init__(self, theme, config=None):
|
||||
self._theme = theme
|
||||
self._widgets = []
|
||||
self._started = False
|
||||
self._config = config
|
||||
|
||||
def started(self):
|
||||
return self._started
|
||||
|
||||
def start(self):
|
||||
"""Print start preamble for i3bar protocol"""
|
||||
self._started = True
|
||||
sys.stdout.write(json.dumps({"version": 1, "click_events": True}) + "\n[\n")
|
||||
|
||||
def stop(self):
|
||||
"""Finish i3bar protocol"""
|
||||
sys.stdout.write("]\n")
|
||||
|
||||
def draw(self, widget, module=None, engine=None):
|
||||
"""
|
||||
Draw a single widget
|
||||
|
||||
Note: technically, this method doesn't draw anything. It only adds
|
||||
blocks of JSON text to self._widgets: one for separator, if the
|
||||
theme contains a separator and one for the widget itself
|
||||
"""
|
||||
widget_drawer = WidgetDrawer(self._theme, self._config)
|
||||
self._widgets.extend(widget_drawer.draw(widget, module, engine))
|
||||
|
||||
def begin(self):
|
||||
"""Start one output iteration"""
|
||||
self._widgets = []
|
||||
self._theme.reset()
|
||||
|
||||
def flush(self):
|
||||
"""Flushes output"""
|
||||
widgets = self._widgets
|
||||
if self._config and self._config.reverse():
|
||||
widgets = list(reversed(widgets))
|
||||
sys.stdout.write(json.dumps(widgets))
|
||||
if len(self._config.unused_keys()) > 0:
|
||||
for key in self._config.unused_keys():
|
||||
log.warning("unused parameter {} - please check the documentation of the affected module to ensure the parameter exists".format(key))
|
||||
|
||||
def end(self):
|
||||
"""Finalizes output"""
|
||||
sys.stdout.write(",\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,80 +0,0 @@
|
|||
"""Pop-up menus."""
|
||||
|
||||
import logging
|
||||
|
||||
try:
|
||||
import Tkinter as tk
|
||||
except ImportError:
|
||||
# python 3
|
||||
try:
|
||||
import tkinter as tk
|
||||
except ImportError:
|
||||
logging.warning("failed to import tkinter - bumblebee popups won't work!")
|
||||
|
||||
class PopupMenu:
|
||||
"""The popup-menu."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
# menu widget
|
||||
self.root = tk.Tk()
|
||||
self.root.withdraw()
|
||||
self.menu = tk.Menu(self.root)
|
||||
|
||||
# internal state
|
||||
self._item_count = 0
|
||||
self._clicked_item = None
|
||||
self._active = False
|
||||
|
||||
# bind event of popup getting closed by clicking outside of its area
|
||||
self.menu.bind('<Unmap>',
|
||||
lambda event: self.root.after_idle(
|
||||
self._dismiss_callback))
|
||||
|
||||
def add_menuitem(self, menuitem, callback=None):
|
||||
"""Add menu items."""
|
||||
item_count = self._item_count
|
||||
|
||||
def click_callback():
|
||||
# call internal callback with item index
|
||||
self._item_callback(item_count)
|
||||
|
||||
# default to internal callback
|
||||
if callback is None:
|
||||
callback = click_callback
|
||||
self.menu.add_command(label=menuitem,
|
||||
command=callback)
|
||||
|
||||
# track item index
|
||||
self._item_count += 1
|
||||
|
||||
def _item_callback(self, which_item):
|
||||
"""Menu item click callback."""
|
||||
logging.debug('popup: item callback: {}'.format(which_item))
|
||||
self._clicked_item = which_item
|
||||
self.root.destroy()
|
||||
self._active = False
|
||||
|
||||
def _dismiss_callback(self):
|
||||
"""Menu dismissed."""
|
||||
logging.debug('popup: menu dismissed')
|
||||
if self._active is True:
|
||||
self._clicked_item = None
|
||||
self.root.destroy()
|
||||
|
||||
def show(self, event):
|
||||
"""Show popup."""
|
||||
self._clicked_item = None
|
||||
self.menu.tk_popup(event['x'], event['y']-50)
|
||||
self._active = True
|
||||
self.root.mainloop()
|
||||
return self._clicked_item
|
||||
|
||||
|
||||
def create_and_show_menu(event, *menuitems):
|
||||
"""Create a menu object and show."""
|
||||
menu_obj = PopupMenu()
|
||||
for menuitem in menuitems:
|
||||
menu_obj.add_menuitem(*menuitem)
|
||||
|
||||
return menu_obj.show(event)
|
|
@ -1,56 +0,0 @@
|
|||
"""Pop-up menus."""
|
||||
|
||||
import logging
|
||||
|
||||
try:
|
||||
import Tkinter as tk
|
||||
except ImportError:
|
||||
# python 3
|
||||
try:
|
||||
import tkinter as tk
|
||||
except ImportError:
|
||||
logging.warning("failed to import tkinter - bumblebee popups won't work!")
|
||||
|
||||
import functools
|
||||
|
||||
class PopupMenu(object):
|
||||
def __init__(self, parent=None, leave=True):
|
||||
|
||||
if not parent:
|
||||
self._root = tk.Tk()
|
||||
self._root.withdraw()
|
||||
self._menu = tk.Menu(self._root, tearoff=0)
|
||||
self._menu.bind("<FocusOut>", self._on_focus_out)
|
||||
else:
|
||||
self._root = parent.root()
|
||||
self._root.withdraw()
|
||||
self._menu = tk.Menu(self._root, tearoff=0)
|
||||
self._menu.bind("<FocusOut>", self._on_focus_out)
|
||||
if leave:
|
||||
self._menu.bind("<Leave>", self._on_focus_out)
|
||||
|
||||
def root(self):
|
||||
return self._root
|
||||
|
||||
def menu(self):
|
||||
return self._menu
|
||||
|
||||
def _on_focus_out(self, event=None):
|
||||
self._root.destroy()
|
||||
|
||||
def _on_click(self, callback):
|
||||
self._root.destroy()
|
||||
callback()
|
||||
|
||||
def add_cascade(self, menuitem, submenu):
|
||||
self._menu.add_cascade(label=menuitem, menu=submenu.menu())
|
||||
|
||||
def add_menuitem(self, menuitem, callback):
|
||||
self._menu.add_command(label=menuitem, command=functools.partial(self._on_click, callback))
|
||||
|
||||
def show(self, event, offset_x=0, offset_y=0):
|
||||
try:
|
||||
self._menu.tk_popup(event['x'] + offset_x, event['y'] + offset_y)
|
||||
finally:
|
||||
self._menu.grab_release()
|
||||
self._root.mainloop()
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue