mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-19 19:25:41 +02:00
Compare commits
479 commits
Author | SHA1 | Date | |
---|---|---|---|
|
43e1944607 | ||
|
ca0f565237 | ||
|
dfa0970b75 | ||
|
dcbb0f88ae | ||
|
6a97b364a0 | ||
|
87c74d54b7 | ||
|
0bcde5c453 | ||
|
56467d0ba3 | ||
|
7e76d53c0a | ||
|
dcf936fd9b | ||
|
e423776000 | ||
|
e68ed8d843 | ||
|
c27de56bea | ||
|
b5450c3918 | ||
|
5a515eae99 | ||
|
b486088f77 | ||
|
a242d3d569 | ||
|
2eb1cda733 | ||
|
21f374d2eb | ||
|
fc24ea225d | ||
|
e1f7c0292f | ||
|
d746d12f6a | ||
|
61d082c802 | ||
|
57711f0dbe | ||
|
b15714b38a | ||
|
3e0083c9f2 | ||
|
a367895dc6 | ||
|
650d1f13f9 | ||
|
e1b6a78f22 | ||
|
20d48a753b | ||
|
37ecc251a4 | ||
|
4826a52306 | ||
|
0f47cbb889 | ||
|
c3f7fe013d | ||
|
b81e41c3c4 | ||
|
060586dbbe | ||
|
c80bae7604 | ||
|
311c481bfe | ||
|
9498d7e445 | ||
|
2d651d1c0e | ||
|
1b2dee55ef | ||
|
700bf5b28c | ||
|
f8ba887dcd | ||
|
887e770202 | ||
|
54902f46ab | ||
|
699c563051 | ||
|
a5ae61b5df | ||
|
568eb1140f | ||
|
3e0a65f185 | ||
|
1a323c6d21 | ||
|
739dc30323 | ||
|
8422e7e0b1 | ||
|
9cc5e0f7a7 | ||
|
3431d5fc75 | ||
|
20659d3350 | ||
|
0bea49b75e | ||
|
70efd7d15c | ||
|
b8a93a2673 | ||
|
a467f56677 | ||
|
b3313cefc6 | ||
|
00234696fe | ||
|
3a7455913f | ||
|
547cef5afb | ||
|
6f3952819f | ||
|
f148c68736 | ||
|
a2d30b96fb | ||
|
cae07a36ff | ||
|
3136310ade | ||
|
be01eeb1de | ||
|
13f46a314a | ||
|
3c572c70c9 | ||
|
b85ba99980 | ||
|
d841aeeecd | ||
|
28a18ad91e | ||
|
da19c09122 | ||
|
58c397d154 | ||
|
53dec73ed2 | ||
|
c44c66c83f | ||
|
4e07b63cef | ||
|
c19a31d925 | ||
|
4066326614 | ||
|
424f22ab84 | ||
|
7a5cf26fb5 | ||
|
89ae7bd743 | ||
|
f2d25c8341 | ||
|
aeeef4f236 | ||
|
195ac5d1cd | ||
|
9d90848291 | ||
|
bbbf2b601e | ||
|
cb2561a72c | ||
|
8b1fa13686 | ||
|
bdc4fbe8e7 | ||
|
e1f78a16ab | ||
|
26bf62a899 | ||
|
cdee55afed | ||
|
9365580539 | ||
|
3a3a711b69 | ||
|
4d46f25854 | ||
|
a943def94e | ||
|
e54e8635e0 | ||
|
176ed4a6f3 | ||
|
d5823bcc4c | ||
|
1283160e17 | ||
|
60671da2ca | ||
![]() |
14550440dd | ||
|
89e74139f5 | ||
|
cbd3bebb04 | ||
|
7fbc1f2c44 | ||
|
9a111a52f5 | ||
|
78f7b60e13 | ||
|
9f5f35a8ac | ||
|
42cef9373e | ||
|
e1fc3a0e29 | ||
|
5db61745a4 | ||
|
4ba193ad0f | ||
|
c4e094de3e | ||
|
1b23e72770 | ||
|
5ac7d51e8a | ||
|
f923261fec | ||
|
8e4d7f04e4 | ||
|
d6e7710a7e | ||
|
f948b9f8f9 | ||
|
6220a07aaf | ||
|
a342e036ad | ||
|
b694fc1583 | ||
|
08f5f444eb | ||
|
d236b9c1b9 | ||
|
7c5ea4fed6 | ||
|
9218ef234c | ||
|
971361b046 | ||
|
5e3859f218 | ||
|
963b9d47ee | ||
|
daeb59e021 | ||
|
3ec6fa1bc7 | ||
|
f21db9caca | ||
|
8ccd79ad08 | ||
|
d1776714ed | ||
|
5da51210de | ||
|
7773a17d57 | ||
|
bdbcc0100a | ||
|
10fde4dd0a | ||
|
134ae847dc | ||
|
f75168796a | ||
|
d09d88b60b | ||
|
0f3894bf63 | ||
|
38a1d0b57c | ||
|
11bef7dd08 | ||
|
146759bd96 | ||
|
73ccafdade | ||
|
02d281df58 | ||
|
e4edbd26c6 | ||
|
310c07b03d | ||
|
2283647fc7 | ||
|
d26d3953f1 | ||
|
c4f820e486 | ||
|
500b051fe4 | ||
|
8fbbce10a5 | ||
|
ac8e45c331 | ||
|
a18296a179 | ||
|
1f25978eb4 | ||
|
c0e0702a6c | ||
|
23d47656e4 | ||
|
4318765030 | ||
|
cb8acf261a | ||
|
63c9c90a61 | ||
|
252a7a1580 | ||
|
710cede371 | ||
|
ede6a541e1 | ||
|
a9ce81b376 | ||
|
a599e7264f | ||
|
1353d635c2 | ||
|
ef7b4ce9b3 | ||
|
06bf127332 | ||
|
b195bc4dcb | ||
|
2e0e1a402f | ||
|
3ca274759a | ||
|
6794193791 | ||
|
95b1f5f261 | ||
|
b22614ecc3 | ||
|
c4cc1b7a36 | ||
|
a53e48a2c1 | ||
|
a5e79d14b7 | ||
|
690bd630a2 | ||
|
9ef6d73663 | ||
|
56b0047004 | ||
|
1a81255579 | ||
|
a14d38b0cb | ||
|
b6450446a8 | ||
|
0cf0d64970 | ||
|
ec9ed66b6b | ||
|
eb26f64ea7 | ||
|
b901ac50ee | ||
|
8d5e8b5f20 | ||
|
f54f583be1 | ||
|
85d55905f9 | ||
|
659b282445 | ||
|
b23365ccac | ||
|
25e123fbe6 | ||
|
aeef3eca0e | ||
|
881359183f | ||
|
4c1398f1a5 | ||
|
f8f0d7ae99 | ||
|
49576a26bf | ||
|
9b93b0794a | ||
|
266a2efbb6 | ||
|
24a3b90a01 | ||
|
8f89545b32 | ||
|
bbd2394601 | ||
|
6fa9c47c0b | ||
|
7ddd009a5c | ||
|
4631e75e28 | ||
|
f5cfc103d0 | ||
|
6027b2728b | ||
|
bd607d7697 | ||
|
302e0d5cc6 | ||
|
19a9f099e2 | ||
|
dcf21f0b06 | ||
|
54c70bb6ad | ||
|
f9fa43845e | ||
|
ec86a7d290 | ||
|
3c3a881638 | ||
|
4a41d4296a | ||
|
463b39b56d | ||
|
87854fa101 | ||
|
8deac539ef | ||
|
794b1ed633 | ||
|
8f1a123de2 | ||
|
028011a816 | ||
|
6ed576c719 | ||
|
8d5deda4e4 | ||
|
4143099e94 | ||
|
d1a8029e6c | ||
|
5da1cd4a38 | ||
|
d002919bad | ||
|
6427e8213c | ||
|
aa09d88d8e | ||
|
2759ba6349 | ||
|
4257c80eee | ||
|
084a9021b9 | ||
|
b331473a6b | ||
|
d15d1f58f7 | ||
|
138db05d70 | ||
|
dd1280f49d | ||
|
8d91cbd8a3 | ||
|
6c10eb2153 | ||
|
a0c07d7836 | ||
|
cb47c53de4 | ||
|
605a6a9ede | ||
|
1b03bd6bc0 | ||
|
36a4250a96 | ||
|
b0e132beaf | ||
|
03e1c7dc13 | ||
|
eb5cb9869c | ||
|
9353aa14fe | ||
|
a8994b9268 | ||
|
8a8a40bfb5 | ||
|
f4ceaaad52 | ||
|
ca077447c2 | ||
|
d9d5671e04 | ||
|
de4814d16e | ||
|
c738f1c63d | ||
|
a3a0334069 | ||
|
5fc092a874 | ||
|
acc20913ef | ||
|
3b5845370c | ||
|
82a3b2ae11 | ||
|
0d878e8b5c | ||
|
4c4a20d835 | ||
|
2b103b7acd | ||
|
4496d82cfb | ||
|
1206153b03 | ||
|
acb8a09376 | ||
|
c1e549df54 | ||
|
9c28d36898 | ||
|
62ca06eccb | ||
|
fce2787bdf | ||
|
43de7c8da8 | ||
|
fd014dc33b | ||
|
068c25d8f6 | ||
|
2b6f5b1e36 | ||
|
4bb81e8940 | ||
|
3ff1c95208 | ||
|
4daa3d9904 | ||
|
ffccabbb13 | ||
|
265188ca4c | ||
|
c44970717b | ||
|
1ce108f24e | ||
|
ca407cd166 | ||
|
a2cf05a64d | ||
|
605490c872 | ||
|
6ac046dec3 | ||
|
2cfe45ee81 | ||
|
1fc6e77926 | ||
|
3cc142a273 | ||
|
5edc226c00 | ||
|
973aa929c1 | ||
|
b8ab8669cb | ||
|
227f9c608a | ||
|
ff238e62ba | ||
|
4fea561c6c | ||
|
52e0b5f3d1 | ||
|
1cf14a7f80 | ||
|
7945a561d0 | ||
|
1d9297593e | ||
|
a59d3cfbd6 | ||
|
f053ddff7d | ||
|
56c4a1c751 | ||
|
2a0a722c13 | ||
|
37e7c2b2c1 | ||
|
0aff641d0c | ||
|
52e2540d42 | ||
|
d9316a202d | ||
|
4ff1c43669 | ||
|
e83c4bd8c1 | ||
|
ae5c7e0fc3 | ||
|
337ce7681f | ||
|
d8d44b0f33 | ||
|
0af9ce354b | ||
|
f7206ef08d | ||
|
375c7ca89c | ||
|
26ea137938 | ||
|
8475ca1603 | ||
|
3e133d8618 | ||
|
1bb77e59f3 | ||
|
9cff50a39e | ||
|
96c75b7f73 | ||
|
ffa86d84a5 | ||
|
5d09e59f11 | ||
|
8a11a3fbe5 | ||
|
cdd0b5b4f0 | ||
|
82ef48f666 | ||
|
f922973450 | ||
|
6612a9af9f | ||
|
f71e7c2905 | ||
|
39a5493450 | ||
|
b562f1310b | ||
|
9f2197ca1c | ||
|
9d94cec549 | ||
|
4f0d27bc7e | ||
|
f166bbbf54 | ||
|
b6931c6ed0 | ||
|
23f12a65b2 | ||
|
72056c50cf | ||
|
11bb45aa87 | ||
|
d40220e511 | ||
|
58038a4236 | ||
|
8324112504 | ||
|
66ec7a85a1 | ||
|
0f6d165084 | ||
|
134141b7c5 | ||
|
f0782d5124 | ||
|
c9abb6b98f | ||
|
31f160141e | ||
|
9ffd305b59 | ||
|
4e96dbd7f7 | ||
|
7d715d81a8 | ||
|
cad9dd8efd | ||
|
bd44e82eca | ||
|
939b81a4ea | ||
|
fee0b91174 | ||
|
4f3aa8c6b0 | ||
|
957e25914c | ||
|
2b670ea612 | ||
|
d7a58e4ee0 | ||
|
94a0154c23 | ||
|
515b36da0d | ||
|
9fe8ef2574 | ||
|
9a1371b96a | ||
|
6250360c58 | ||
|
77303e8173 | ||
|
4cd031bc73 | ||
|
03476e9360 | ||
|
ec465ba3b3 | ||
|
f8e544ae05 | ||
|
82c92185ea | ||
|
bfff39b7c9 | ||
|
804af178d5 | ||
|
6a9f66b837 | ||
|
2e6847077e | ||
|
26892c66b2 | ||
|
1bcf116859 | ||
|
0f389435ca | ||
|
fa760ffa65 | ||
|
76225a8366 | ||
|
87f38d19a0 | ||
|
535d49e9c3 | ||
|
8709e8da38 | ||
|
7bbcad55e4 | ||
|
185f313580 | ||
|
13b5934e65 | ||
|
e723b039ad | ||
|
12f7802537 | ||
|
ba5b28f437 | ||
|
60904bb18f | ||
|
8076a8da2a | ||
|
3ff9fcab48 | ||
|
dd19f070b3 | ||
|
ab0323e35e | ||
|
42d944d020 | ||
|
6fa118a5e5 | ||
|
7a5ce73c94 | ||
|
eff890ab9d | ||
|
8c2e5d8bde | ||
|
f0b16033fe | ||
|
a6194c63e6 | ||
|
e201cc3d30 | ||
|
0da24198b3 | ||
|
4ab3263ebb | ||
|
dabb2e1407 | ||
|
d450bf12a1 | ||
|
e8a2f8df9a | ||
|
066427d77a | ||
|
25379b7e1f | ||
|
01ee028c4d | ||
|
a685dadb75 | ||
|
b27eff36f9 | ||
|
9b3548736a | ||
|
5249d9ef79 | ||
|
d39e6b8b94 | ||
|
e4e9587322 | ||
|
e9d762fa03 | ||
|
149798fe98 | ||
|
1079bca3eb | ||
|
d1c7647b03 | ||
|
7c7c4e7ce9 | ||
|
b8dd247111 | ||
|
427e0ce418 | ||
|
7ca22a6dab | ||
|
af0b7e57d8 | ||
|
def90edde1 | ||
|
dab6428859 | ||
|
73e1d328c3 | ||
|
ca43eb3016 | ||
|
0abc0f37dd | ||
|
103c3102a9 | ||
|
621f0e18e6 | ||
|
9681e0aabe | ||
|
589a6f528a | ||
|
01c6fc5f52 | ||
|
fe6cc43ad8 | ||
|
2173e0dc4d | ||
|
560d7464b4 | ||
|
eb94c8cceb | ||
|
7e7c011126 | ||
|
0963afd282 | ||
|
36de95cc2a | ||
|
a5be550964 | ||
|
360e1fbada | ||
|
5af070ee1d | ||
|
25c20e5534 | ||
|
db12ceb026 | ||
|
ae7d54fb80 | ||
|
591cae4c6d | ||
|
be6e714eb0 | ||
|
8b6b82f1e5 | ||
|
b00954045b | ||
|
495a4c8fb1 | ||
|
be10465a3b | ||
|
7d3851046e | ||
|
58a52512dd | ||
|
910522262f | ||
|
315044d342 | ||
|
ba7b9e6244 | ||
|
1c6c73928b | ||
|
74016d7d33 | ||
|
96a35e5304 | ||
|
a210d33320 | ||
|
3072c2b13f | ||
|
64df5d9806 | ||
|
7d94631991 | ||
|
741107d31c | ||
|
b97ba80aea | ||
|
8c095eb423 | ||
|
30f2880be7 | ||
|
b4ce851b4d | ||
|
239ce16a79 | ||
|
7da13a26d0 | ||
|
21adc40a52 | ||
|
b1932872ce |
129 changed files with 14805 additions and 3266 deletions
|
@ -19,13 +19,17 @@ packages:
|
|||
- json-c-dev
|
||||
- libmpdclient-dev
|
||||
- alsa-lib-dev
|
||||
- pulseaudio-dev
|
||||
- pipewire-dev
|
||||
- ttf-dejavu
|
||||
- gcovr
|
||||
- python3
|
||||
- py3-pip
|
||||
- flex
|
||||
- bison
|
||||
|
||||
sources:
|
||||
- https://codeberg.org/dnkl/yambar
|
||||
- https://git.sr.ht/~dnkl/yambar
|
||||
|
||||
# triggers:
|
||||
# - action: email
|
||||
|
@ -33,10 +37,10 @@ sources:
|
|||
# to: <comitter>
|
||||
|
||||
tasks:
|
||||
- codespell: |
|
||||
pip install codespell
|
||||
cd yambar
|
||||
~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd
|
||||
- fcft: |
|
||||
cd yambar/subprojects
|
||||
git clone https://codeberg.org/dnkl/fcft.git
|
||||
cd ../..
|
||||
- setup: |
|
||||
mkdir -p bld/debug bld/release bld/x11-only bld/wayland-only bld/plugs-are-shared
|
||||
meson --buildtype=debug -Db_coverage=true yambar bld/debug
|
||||
|
|
24
.clang-format
Normal file
24
.clang-format
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
BasedOnStyle: GNU
|
||||
IndentWidth: 4
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Auto
|
||||
PointerAlignment: Right
|
||||
ColumnLimit: 120
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterEnum: false
|
||||
AfterClass: false
|
||||
SplitEmptyFunction: true
|
||||
AfterFunction: true
|
||||
AfterStruct: false
|
||||
|
||||
SpaceBeforeParens: ControlStatements
|
||||
Cpp11BracedListStyle: true
|
||||
|
||||
WhitespaceSensitiveMacros:
|
||||
- REGISTER_CORE_PARTICLE
|
||||
- REGISTER_CORE_DECORATION
|
||||
- REGISTER_CORE_PLUGIN
|
||||
- REGISTER_CORE_MODULE
|
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 70
|
||||
|
||||
[{meson.build,PKGBUILD}]
|
||||
indent_size = 2
|
||||
|
||||
[*.scd]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = false
|
|
@ -1,98 +0,0 @@
|
|||
image: alpine:latest
|
||||
|
||||
stages:
|
||||
- info
|
||||
- build
|
||||
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
|
||||
before_script:
|
||||
- apk update
|
||||
- apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc
|
||||
- apk add pixman-dev freetype-dev fontconfig-dev
|
||||
- apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev
|
||||
- apk add wayland-dev wayland-protocols wlroots-dev
|
||||
- apk add json-c-dev libmpdclient-dev alsa-lib-dev
|
||||
- apk add ttf-dejavu
|
||||
- apk add git
|
||||
|
||||
versions:
|
||||
stage: info
|
||||
script:
|
||||
- meson --version
|
||||
- ninja --version
|
||||
- cc --version
|
||||
|
||||
debug:
|
||||
stage: build
|
||||
script:
|
||||
- apk add gcovr
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Db_coverage=true ../..
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ninja coverage-html
|
||||
- mv meson-logs/coveragereport ../../coverage
|
||||
- ninja coverage-text
|
||||
- tail -2 meson-logs/coverage.txt
|
||||
artifacts:
|
||||
paths:
|
||||
- coverage
|
||||
coverage: '/^TOTAL.*\s+(\d+\%)$/'
|
||||
|
||||
# valgrind:
|
||||
# stage: build
|
||||
# script:
|
||||
# - apk add valgrind
|
||||
# - mkdir -p bld/debug
|
||||
# - cd bld/debug
|
||||
# - meson --buildtype=debug ../..
|
||||
# - ninja -k0
|
||||
# - meson test --verbose --wrapper "valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=3"
|
||||
|
||||
release:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/release
|
||||
- cd bld/release
|
||||
- meson --buildtype=minsize ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
x11_only:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
wayland_only:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
plugins_as_shared_modules:
|
||||
stage: build
|
||||
script:
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
|
||||
codespell:
|
||||
image: alpine:latest
|
||||
stage: build
|
||||
script:
|
||||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- pip install codespell
|
||||
- codespell README.md CHANGELOG.md *.c *.h doc/*.scd
|
132
.woodpecker.yaml
Normal file
132
.woodpecker.yaml
Normal file
|
@ -0,0 +1,132 @@
|
|||
steps:
|
||||
- name: codespell
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk add openssl
|
||||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- python3 -m venv codespell-venv
|
||||
- source codespell-venv/bin/activate
|
||||
- pip install codespell
|
||||
- codespell README.md CHANGELOG.md *.c *.h doc/*.scd bar decorations modules particles examples
|
||||
- deactivate
|
||||
|
||||
- name: subprojects
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk add git
|
||||
- mkdir -p subprojects && cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/tllist.git
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
|
||||
- name: x64
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
depends_on: [subprojects]
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk update
|
||||
- apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc
|
||||
- apk add pixman-dev freetype-dev fontconfig-dev
|
||||
- apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev
|
||||
- apk add wayland-dev wayland-protocols wlroots-dev
|
||||
- apk add json-c-dev libmpdclient-dev alsa-lib-dev pulseaudio-dev pipewire-dev
|
||||
- apk add ttf-dejavu
|
||||
- apk add git
|
||||
- apk add flex bison
|
||||
|
||||
# Debug
|
||||
- apk add gcovr
|
||||
- mkdir -p bld/debug-x64
|
||||
- cd bld/debug-x64
|
||||
- meson --buildtype=debug -Db_coverage=true ../..
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ninja coverage-html
|
||||
- mv meson-logs/coveragereport ../../coverage
|
||||
- ninja coverage-text
|
||||
- tail -2 meson-logs/coverage.txt
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Release
|
||||
- mkdir -p bld/release-x64
|
||||
- cd bld/release-x64
|
||||
- meson --buildtype=minsize ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# X11 only
|
||||
- mkdir -p bld/x11-only
|
||||
- cd bld/x11-only
|
||||
- meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Wayland only
|
||||
- mkdir -p bld/wayland-only
|
||||
- cd bld/wayland-only
|
||||
- meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
- name: x86
|
||||
when:
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
depends_on: [subprojects]
|
||||
image: i386/alpine:latest
|
||||
commands:
|
||||
- apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc
|
||||
- apk add pixman-dev freetype-dev fontconfig-dev
|
||||
- apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev
|
||||
- apk add wayland-dev wayland-protocols wlroots-dev
|
||||
- apk add json-c-dev libmpdclient-dev alsa-lib-dev pulseaudio-dev pipewire-dev
|
||||
- apk add ttf-dejavu
|
||||
- apk add git
|
||||
- apk add flex bison
|
||||
|
||||
# Debug
|
||||
- mkdir -p bld/debug-x86
|
||||
- cd bld/debug-x86
|
||||
- meson --buildtype=debug ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Release
|
||||
- mkdir -p bld/release-x86
|
||||
- cd bld/release-x86
|
||||
- meson --buildtype=minsize ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
||||
|
||||
# Plugins as shared modules
|
||||
- mkdir -p bld/shared-modules
|
||||
- cd bld/shared-modules
|
||||
- meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../
|
||||
- ninja -k0
|
||||
- meson test --print-errorlogs
|
||||
- ./yambar --version
|
||||
- cd ../..
|
525
CHANGELOG.md
525
CHANGELOG.md
|
@ -1,50 +1,535 @@
|
|||
# Changelog
|
||||
|
||||
* [Unreleased](#unreleased)
|
||||
* [1.11.0](#1-11-0)
|
||||
* [1.10.0](#1-10-0)
|
||||
* [1.9.0](#1-9-0)
|
||||
* [1.8.0](#1-8-0)
|
||||
* [1.7.0](#1-7-0)
|
||||
* [1.6.2](#1-6-2)
|
||||
* [1.6.1](#1-6-1)
|
||||
* [1.6.0](#1-6-0)
|
||||
* [1.5.0](#1-5-0)
|
||||
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
|
||||
* environment variable substitution in config files ([#96][96]).
|
||||
* Log output now respects the [`NO_COLOR`](http://no-color.org/)
|
||||
environment variable.
|
||||
* network: `type` tag ([#380][380]).
|
||||
* network: `type` and `kind` tags ([#380][380]).
|
||||
* tags: `/<N>` tag formatter: divides the tag's decimal value with `N`
|
||||
([#392][392]).
|
||||
* i3/sway: `output` tag, reflecting the output (monitor) a workspace
|
||||
is on.
|
||||
* Added "string like" `~~` operator to Map particle. Allows glob-style
|
||||
matching on strings using `*` and `?` characters ([#400][400]).
|
||||
* Added "single" mode flag to the `mpd` module ([#428][428]).
|
||||
* niri: add a new module for niri-workspaces and niri-language
|
||||
([#404][404]).
|
||||
* pipewire: added `spacing`, `left-spacing` and `right-spacing`
|
||||
attributes.
|
||||
* mpris: new module ([#53][53]).
|
||||
|
||||
[96]: https://codeberg.org/dnkl/yambar/issues/96
|
||||
[380]: https://codeberg.org/dnkl/yambar/issues/380
|
||||
[392]: https://codeberg.org/dnkl/yambar/issues/392
|
||||
[400]: https://codeberg.org/dnkl/yambar/pulls/400
|
||||
[428]: https://codeberg.org/dnkl/yambar/pulls/428
|
||||
[404]: https://codeberg.org/dnkl/yambar/issues/404
|
||||
[53]: https://codeberg.org/dnkl/yambar/issues/53
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* `river`: expand to an empty list of particles when river is not
|
||||
running ([#384][384]).
|
||||
|
||||
[384]: https://codeberg.org/dnkl/yambar/issues/384
|
||||
|
||||
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
|
||||
* network: fix missing break in switch statement ([#377][377]).
|
||||
* i3/sway: crash when output is turned off an on ([#300][300]).
|
||||
* mpd: yambar never attempting to reconnect after MPD closed the
|
||||
connection (for example, when MPD is restarted).
|
||||
* Bar positioning on multi-monitor setups, when `location=bottom`.
|
||||
* pipewire: Improve handling of node switching ([#424][424]).
|
||||
|
||||
[377]: https://codeberg.org/dnkl/yambar/issues/377
|
||||
[300]: https://codeberg.org/dnkl/yambar/issues/300
|
||||
[424]: https://codeberg.org/dnkl/yambar/pulls/424
|
||||
|
||||
|
||||
### Security
|
||||
### Contributors
|
||||
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Added
|
||||
|
||||
* battery: current smoothing, for improved discharge estimates.
|
||||
* battery: scale option, for batteries that report 'charge' at a
|
||||
different scale than 'current'.
|
||||
* network: new `quality` tag (Wi-Fi only).
|
||||
* Read alternative config from pipes and FIFOs (e.g. `--config
|
||||
/dev/stdin`) ([#340][340]).
|
||||
* Added `overlay` and `background` as possible `layer` values
|
||||
([#372][372]).
|
||||
|
||||
[340]: https://codeberg.org/dnkl/yambar/pulls/340
|
||||
[372]: https://codeberg.org/dnkl/yambar/issues/372
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* log-level: default to `warning`
|
||||
* network: use dynlist instead of fixed name ([#355][355])
|
||||
|
||||
[355]: https://codeberg.org/dnkl/yambar/pulls/355
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Compiler error _‘fmt’ may be used uninitialized_ ([#311][311]).
|
||||
* map: conditions failing to match when they contain multiple, quoted
|
||||
tag values ([#302][302]).
|
||||
* Crash when hidden by an opaque window.
|
||||
* Bar not resizing itself when the screen resolution is changed
|
||||
([#330][330]).
|
||||
* i3/sway: incorrect empty/title state of workspaces ([#343][343]).
|
||||
* mem: state updated on each bar redraw ([#352][352]).
|
||||
* script: buffer overflow when reading large amounts of data.
|
||||
* i3/sway: module fails when reloading config file ([#361][361]).
|
||||
* Worked around bug in gcc causing a compilation error ([#350][350]).
|
||||
* Miscalculation of list width in presence of empty particles ([#369][369]).
|
||||
* Log-level not respected by syslog.
|
||||
|
||||
[311]: https://codeberg.org/dnkl/yambar/issues/311
|
||||
[302]: https://codeberg.org/dnkl/yambar/issues/302
|
||||
[330]: https://codeberg.org/dnkl/yambar/issues/330
|
||||
[343]: https://codeberg.org/dnkl/yambar/issues/343
|
||||
[352]: https://codeberg.org/dnkl/yambar/issues/352
|
||||
[361]: https://codeberg.org/dnkl/yambar/issues/361
|
||||
[350]: https://codeberg.org/dnkl/yambar/issues/350
|
||||
[369]: https://codeberg.org/dnkl/yambar/issues/369
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* Delgan
|
||||
* Haden Collins
|
||||
* Jordan Isaacs
|
||||
* kotyk
|
||||
* Leonardo Hernández Hernández
|
||||
* oob
|
||||
* rdbo
|
||||
* Sertonix
|
||||
* steovd
|
||||
* Väinö Mäkelä
|
||||
* Yiyu Zhou
|
||||
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### Added
|
||||
|
||||
* Field width tag format option ([#246][246])
|
||||
* river: support for ‘layout’ events.
|
||||
* dwl: support for specifying name of tags ([#256][256])
|
||||
* i3/sway: extend option `sort`; use `native` to sort numbered workspaces only.
|
||||
* modules/dwl: handle the appid status ([#284][284])
|
||||
* battery: also show estimation for time to full ([#303][303]).
|
||||
* on-click: tilde expansion ([#307][307])
|
||||
* script: tilde expansion of `path` ([#307][307]).
|
||||
|
||||
[246]: https://codeberg.org/dnkl/yambar/issues/246
|
||||
[256]: https://codeberg.org/dnkl/yambar/pulls/256
|
||||
[284]: https://codeberg.org/dnkl/yambar/pulls/284
|
||||
[307]: https://codeberg.org/dnkl/yambar/issues/307
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* disk-io: `interval` renamed to `poll-interval`
|
||||
* mem: `interval` renamed to `poll-interval`
|
||||
* battery/network/script: `poll-interval` unit changed from seconds to
|
||||
milliseconds ([#244][244]).
|
||||
* all modules: minimum poll interval changed from 500ms to 250ms.
|
||||
* network: do not use IPv6 link-local ([#281][281])
|
||||
|
||||
[244]: https://codeberg.org/dnkl/yambar/issues/244
|
||||
[281]: https://codeberg.org/dnkl/yambar/pulls/281
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Build failures for certain combinations of enabled and disabled
|
||||
plugins ([#239][239]).
|
||||
* Documentation for the `cpu` module; `interval` has been renamed to
|
||||
`poll-interval` ([#241][241]).
|
||||
* battery: module was not thread safe.
|
||||
* dwl module reporting only the last part of the title ([#251][251])
|
||||
* i3/sway: regression; persistent workspaces shown twice
|
||||
([#253][253]).
|
||||
* pipewire: use `roundf()` instead of `ceilf()` for more accuracy
|
||||
([#262][262])
|
||||
* Crash when a yaml anchor has a value that already exists in the
|
||||
target yaml node ([#286][286]).
|
||||
* battery: Fix time conversion in battery estimation ([#303][303]).
|
||||
* battery: poll timeout being reset when receiving irrelevant udev
|
||||
notification (leading to battery status never updating, in worst
|
||||
case) ([#305][305]).
|
||||
|
||||
[239]: https://codeberg.org/dnkl/yambar/issues/239
|
||||
[241]: https://codeberg.org/dnkl/yambar/issues/241
|
||||
[251]: https://codeberg.org/dnkl/yambar/pulls/251
|
||||
[253]: https://codeberg.org/dnkl/yambar/issues/253
|
||||
[262]: https://codeberg.org/dnkl/yambar/issues/262
|
||||
[286]: https://codeberg.org/dnkl/yambar/issues/286
|
||||
[305]: https://codeberg.org/dnkl/yambar/issues/305
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* Leonardo Gibrowski Faé (Horus)
|
||||
* Armin Fisslthaler
|
||||
* Ben Brown
|
||||
* David Bimmler
|
||||
* Leonardo Hernández Hernández
|
||||
* Ogromny
|
||||
* Oleg Hahm
|
||||
* Stanislav Ochotnický
|
||||
* tiosgz
|
||||
* Yutaro Ohno
|
||||
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Added
|
||||
|
||||
* Support for specifying number of decimals when printing a float tag
|
||||
([#200][200]).
|
||||
* Support for custom font fallbacks ([#153][153]).
|
||||
* overline: new decoration ([#153][153]).
|
||||
* i3/sway: boolean option `strip-workspace-numbers`.
|
||||
* font-shaping: new inheritable configuration option, allowing you to
|
||||
configure whether strings should be _shaped_ using HarfBuzz, or not
|
||||
([#159][159]).
|
||||
* river: support for the new “mode” event present in version 3 of the
|
||||
river status manager protocol, in the form of a new tag, _”mode”_,
|
||||
in the `title` particle.
|
||||
* network: request link stats and expose under tags `dl-speed` and
|
||||
`ul-speed` when `poll-interval` is set.
|
||||
* new module: disk-io.
|
||||
* new module: pulse ([#223][223]).
|
||||
* alsa: `dB` tag ([#202][202]).
|
||||
* mpd: `file` tag ([#219][219]).
|
||||
* pipewire: add a new module for pipewire ([#224][224])
|
||||
* on-click: support `next`/`previous` mouse buttons ([#228][228]).
|
||||
* dwl: add a new module for DWL ([#218][218])
|
||||
* sway: support for workspace ‘rename’ and ‘move’ events
|
||||
([#216][216]).
|
||||
|
||||
[153]: https://codeberg.org/dnkl/yambar/issues/153
|
||||
[159]: https://codeberg.org/dnkl/yambar/issues/159
|
||||
[200]: https://codeberg.org/dnkl/yambar/issues/200
|
||||
[202]: https://codeberg.org/dnkl/yambar/issues/202
|
||||
[218]: https://codeberg.org/dnkl/yambar/pulls/218
|
||||
[219]: https://codeberg.org/dnkl/yambar/pulls/219
|
||||
[223]: https://codeberg.org/dnkl/yambar/pulls/223
|
||||
[224]: https://codeberg.org/dnkl/yambar/pulls/224
|
||||
[228]: https://codeberg.org/dnkl/yambar/pulls/228
|
||||
[216]: https://codeberg.org/dnkl/yambar/issues/216
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* All modules are now compile-time optional.
|
||||
* Minimum required meson version is now 0.59.
|
||||
* Float tags are now treated as floats instead of integers when
|
||||
formatted with the `kb`/`kib`/`mb`/`mib`/`gb`/`gib` string particle
|
||||
formatters.
|
||||
* network: `tx-bitrate` and `rx-bitrate` are now in bits/s instead of
|
||||
Mb/s. Use the `mb` string formatter to render these tags as before
|
||||
(e.g. `string: {text: "{tx-bitrate:mb}"}`).
|
||||
* i3: newly created, and **unfocused** workspaces are now considered
|
||||
non-empty ([#191][191])
|
||||
* alsa: use dB instead of raw volume values, if possible, when
|
||||
calculating the `percent` tag ([#202][202])
|
||||
* cpu: `content` particle is now a template instantiated once for each
|
||||
core, and once for the total CPU usage. See
|
||||
**yambar-modules-cpu**(5) for more information ([#207][207]).
|
||||
* **BREAKING CHANGE**: overhaul of the `map` particle. Instead of
|
||||
specifying a `tag` and then an array of `values`, you must now
|
||||
simply use an array of `conditions`, that consist of:
|
||||
|
||||
`<tag> <operation> <value>`
|
||||
|
||||
where `<operation>` is one of:
|
||||
|
||||
`== != < <= > >=`
|
||||
|
||||
Note that boolean tags must be used as is:
|
||||
|
||||
`online`
|
||||
|
||||
`~online # use '~' to match for their falsehood`
|
||||
|
||||
As an example, if you previously had something like:
|
||||
|
||||
```
|
||||
map:
|
||||
tag: State
|
||||
values:
|
||||
unrecognized:
|
||||
...
|
||||
```
|
||||
|
||||
You would now write it as:
|
||||
|
||||
```
|
||||
map:
|
||||
conditions:
|
||||
State == unrecognized:
|
||||
...
|
||||
```
|
||||
|
||||
Note that if `<value>` contains any non-alphanumerical characters,
|
||||
it **must** be surrounded by `""`:
|
||||
|
||||
`State == "very confused!!!"`
|
||||
|
||||
Finally, you can mix and match conditions using the boolean
|
||||
operators `&&` and `||`:
|
||||
|
||||
```
|
||||
<condition1> && <condition2>
|
||||
<condition1> && (<condition2> || <condition3>) # parenthesis work
|
||||
~(<condition1> && <condition2>) # '~' can be applied to any condition
|
||||
```
|
||||
|
||||
For a more thorough explanation, see the updated map section in the
|
||||
man page for yambar-particles([#137][137], [#175][175] and [#][182]).
|
||||
|
||||
[137]: https://codeberg.org/dnkl/yambar/issues/137
|
||||
[175]: https://codeberg.org/dnkl/yambar/issues/172
|
||||
[182]: https://codeberg.org/dnkl/yambar/issues/182
|
||||
[191]: https://codeberg.org/dnkl/yambar/issues/191
|
||||
[202]: https://codeberg.org/dnkl/yambar/issues/202
|
||||
[207]: https://codeberg.org/dnkl/yambar/issues/207
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* i3: fixed “missing workspace indicator” (_err: modules/i3.c:94:
|
||||
workspace reply/event without 'name' and/or 'output', and/or 'focus'
|
||||
properties_).
|
||||
* Slow/laggy behavior when quickly spawning many `on-click` handlers,
|
||||
e.g. when handling mouse wheel events ([#169][169]).
|
||||
* cpu: don’t error out on systems where SMT has been disabled
|
||||
([#172][172]).
|
||||
* examples/dwl-tags: updated parsing of `output` name ([#178][178]).
|
||||
* sway-xkb: don’t crash when Sway sends an _”added”_ event for a
|
||||
device yambar is already tracking ([#177][177]).
|
||||
* Crash when a particle is “too wide”, and tries to render outside the
|
||||
bar ([#198][198]).
|
||||
* string: crash when failing to convert string to UTF-32.
|
||||
* script: only first transaction processed when receiving multiple
|
||||
transactions in a single batch ([#221][221]).
|
||||
* network: missing SSID (recent kernels, or possibly wireless drivers,
|
||||
no longer provide the SSID in the `NL80211_CMD_NEW_STATION`
|
||||
response) ([#226][226]).
|
||||
* sway-xkb: crash when compositor presents multiple inputs with
|
||||
identical IDs ([#229][229]).
|
||||
|
||||
[169]: https://codeberg.org/dnkl/yambar/issues/169
|
||||
[172]: https://codeberg.org/dnkl/yambar/issues/172
|
||||
[178]: https://codeberg.org/dnkl/yambar/issues/178
|
||||
[177]: https://codeberg.org/dnkl/yambar/issues/177
|
||||
[198]: https://codeberg.org/dnkl/yambar/issues/198
|
||||
[221]: https://codeberg.org/dnkl/yambar/issues/221
|
||||
[226]: https://codeberg.org/dnkl/yambar/issues/226
|
||||
[229]: https://codeberg.org/dnkl/yambar/issues/229
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* Baptiste Daroussin
|
||||
* Horus
|
||||
* Johannes
|
||||
* Leonardo Gibrowski Faé
|
||||
* Leonardo Neumann
|
||||
* Midgard
|
||||
* Ogromny
|
||||
* Peter Rice
|
||||
* Timur Celik
|
||||
* Willem van de Krol
|
||||
* hiog
|
||||
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Added
|
||||
|
||||
* ramp: can now have custom min and max values
|
||||
([#103](https://codeberg.org/dnkl/yambar/issues/103)).
|
||||
* border: new decoration.
|
||||
* i3/sway: new boolean tag: `empty`
|
||||
([#139](https://codeberg.org/dnkl/yambar/issues/139)).
|
||||
* mem: a module handling system memory monitoring
|
||||
* cpu: a module offering cpu usage monitoring
|
||||
* removables: support for audio CDs
|
||||
([#146](https://codeberg.org/dnkl/yambar/issues/146)).
|
||||
* removables: new boolean tag: `audio`.
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* fcft >= 3.0 is now required.
|
||||
* Made `libmpdclient` an optional dependency
|
||||
* battery: unknown battery states are now mapped to ‘unknown’, instead
|
||||
of ‘discharging’.
|
||||
* Wayland: the bar no longer exits when the monitor is
|
||||
disabled/unplugged ([#106](https://codeberg.org/dnkl/yambar/issues/106)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* `left-margin` and `right-margin` from being rejected as invalid
|
||||
options.
|
||||
* Crash when `udev_monitor_receive_device()` returned `NULL`. This
|
||||
affected the “backlight”, “battery” and “removables” modules
|
||||
([#109](https://codeberg.org/dnkl/yambar/issues/109)).
|
||||
* foreign-toplevel: update bar when a top-level is closed.
|
||||
* Bar not being mapped on an output before at least one module has
|
||||
“refreshed” it ([#116](https://codeberg.org/dnkl/yambar/issues/116)).
|
||||
* network: failure to retrieve wireless attributes (SSID, RX/TX
|
||||
bitrate, signal strength etc).
|
||||
* Integer options that were supposed to be >= 0 were incorrectly
|
||||
allowed, leading to various bad things; including yambar crashing,
|
||||
or worse, the compositor crashing
|
||||
([#129](https://codeberg.org/dnkl/yambar/issues/129)).
|
||||
* kib/kb, mib/mb and gib/gb formatters were inverted.
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* [sochotnicky](https://codeberg.org/sochotnicky)
|
||||
* Alexandre Acebedo
|
||||
* anb
|
||||
* Baptiste Daroussin
|
||||
* Catterwocky
|
||||
* horus645
|
||||
* Jan Beich
|
||||
* mz
|
||||
* natemaia
|
||||
* nogerine
|
||||
* Soc Virnyl S. Estela
|
||||
* Vincent Fischer
|
||||
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Added
|
||||
|
||||
* i3: `persistent` attribute, allowing persistent workspaces
|
||||
([#72](https://codeberg.org/dnkl/yambar/issues/72)).
|
||||
* bar: `border.{left,right,top,bottom}-width`, allowing the width of
|
||||
each side of the border to be configured
|
||||
individually. `border.width` is now a short-hand for setting all
|
||||
four borders to the same value
|
||||
([#77](https://codeberg.org/dnkl/yambar/issues/77)).
|
||||
* bar: `layer: top|bottom`, allowing the layer which the bar is
|
||||
rendered on to be changed. Wayland only - ignored on X11.
|
||||
* river: `all-monitors: false|true`.
|
||||
* `-d,--log-level=info|warning|error|none` command line option
|
||||
([#84](https://codeberg.org/dnkl/yambar/issues/84)).
|
||||
* river: support for the river-status protocol, version 2 (‘urgent’
|
||||
views).
|
||||
* `online` tag to the `alsa` module.
|
||||
* alsa: `volume` and `muted` options, allowing you to configure which
|
||||
channels to use as source for the volume level and muted state.
|
||||
* foreign-toplevel: Wayland module that provides information about
|
||||
currently opened windows.
|
||||
* alsa: support for capture devices.
|
||||
* network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags.
|
||||
* network: `poll-interval` option (for the new `signal` and
|
||||
`*-bitrate` tags).
|
||||
* tags: percentage tag formatter, for range tags: `{tag_name:%}`.
|
||||
* tags: kb/mb/gb, and kib/mib/gib tag formatters.
|
||||
* clock: add a config option to show UTC time.
|
||||
|
||||
### Changed
|
||||
|
||||
* bar: do not add `spacing` around empty (zero-width) modules.
|
||||
* alsa: do not error out if we fail to connect to the ALSA device, or
|
||||
if we get disconnected. Instead, keep retrying until we succeed
|
||||
([#86](https://codeberg.org/dnkl/yambar/issues/86)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* `yambar --backend=wayland` always erroring out with _”yambar was
|
||||
compiled without the Wayland backend”_.
|
||||
* Regression: `{where}` tag not being expanded in progress-bar
|
||||
`on-click` handlers.
|
||||
* `alsa` module causing yambar to use 100% CPU if the ALSA device is
|
||||
disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)).
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
* [paemuri](https://codeberg.org/paemuri)
|
||||
* [ericonr](https://codeberg.org/ericonr)
|
||||
* [Nulo](https://nulo.in)
|
||||
|
||||
|
||||
## 1.6.2
|
||||
|
||||
### Added
|
||||
|
||||
* Text shaping support.
|
||||
* Support for middle and right mouse buttons, mouse wheel and trackpad
|
||||
scrolling (https://codeberg.org/dnkl/yambar/issues/39).
|
||||
scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)).
|
||||
* script: polling mode. See the new `poll-interval` option
|
||||
(https://codeberg.org/dnkl/yambar/issues/67).
|
||||
([#67](https://codeberg.org/dnkl/yambar/issues/67)).
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* doc: split up **yambar-modules**(5) into multiple man pages, one for
|
||||
each module (https://codeberg.org/dnkl/yambar/issues/15).
|
||||
each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)).
|
||||
* fcft >= 2.4.0 is now required.
|
||||
* sway-xkb: non-keyboard inputs are now ignored
|
||||
(https://codeberg.org/dnkl/yambar/issues/51).
|
||||
([#51](https://codeberg.org/dnkl/yambar/issues/51)).
|
||||
* battery: don’t terminate (causing last status to “freeze”) when
|
||||
failing to update; retry again later
|
||||
(https://codeberg.org/dnkl/yambar/issues/44).
|
||||
([#44](https://codeberg.org/dnkl/yambar/issues/44)).
|
||||
* battery: differentiate "Not Charging" and "Discharging" in state
|
||||
tag of battery module.
|
||||
(https://codeberg.org/dnkl/yambar/issues/57).
|
||||
([#57](https://codeberg.org/dnkl/yambar/issues/57)).
|
||||
* string: use HORIZONTAL ELLIPSIS instead of three regular periods
|
||||
when truncating a string
|
||||
(https://codeberg.org/dnkl/yambar/issues/73).
|
||||
([#73](https://codeberg.org/dnkl/yambar/issues/73)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Crash when merging non-dictionary anchors in the YAML configuration
|
||||
(https://codeberg.org/dnkl/yambar/issues/32).
|
||||
([#32](https://codeberg.org/dnkl/yambar/issues/32)).
|
||||
* Crash in the `ramp` particle when the tag’s value was out-of-bounds
|
||||
(https://codeberg.org/dnkl/yambar/issues/45).
|
||||
([#45](https://codeberg.org/dnkl/yambar/issues/45)).
|
||||
* Crash when a string particle contained `{}`
|
||||
(https://codeberg.org/dnkl/yambar/issues/48).
|
||||
([#48](https://codeberg.org/dnkl/yambar/issues/48)).
|
||||
* `script` module rejecting range tag end values containing the digit
|
||||
`9` (https://codeberg.org/dnkl/yambar/issues/60).
|
||||
`9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)).
|
||||
|
||||
|
||||
### Contributors
|
||||
|
@ -59,7 +544,7 @@
|
|||
|
||||
* i3: workspaces with numerical names are sorted separately from
|
||||
non-numerically named workspaces
|
||||
(https://codeberg.org/dnkl/yambar/issues/30).
|
||||
([#30](https://codeberg.org/dnkl/yambar/issues/30)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
@ -67,7 +552,7 @@
|
|||
* mpd: `elapsed` tag not working (regression, introduced in 1.6.0).
|
||||
* Wrong background color for (semi-) transparent backgrounds.
|
||||
* battery: stats sometimes getting stuck at 0, or impossibly large
|
||||
values (https://codeberg.org/dnkl/yambar/issues/25).
|
||||
values ([#25](https://codeberg.org/dnkl/yambar/issues/25)).
|
||||
|
||||
|
||||
## 1.6.0
|
||||
|
@ -76,17 +561,17 @@
|
|||
|
||||
* alsa: `percent` tag. This is an integer tag that represents the
|
||||
current volume as a percentage value
|
||||
(https://codeberg.org/dnkl/yambar/issues/10).
|
||||
([#10](https://codeberg.org/dnkl/yambar/issues/10)).
|
||||
* river: added documentation
|
||||
(https://codeberg.org/dnkl/yambar/issues/9).
|
||||
([#9](https://codeberg.org/dnkl/yambar/issues/9)).
|
||||
* script: new module, adds support for custom user scripts
|
||||
(https://codeberg.org/dnkl/yambar/issues/11).
|
||||
([#11](https://codeberg.org/dnkl/yambar/issues/11)).
|
||||
* mpd: `volume` tag. This is a range tag that represents MPD's current
|
||||
volume in percentage (0-100)
|
||||
* i3: `sort` configuration option, that controls how the workspace
|
||||
list is sorted. Can be set to one of `none`, `ascending` or
|
||||
`descending`. Default is `none`
|
||||
(https://codeberg.org/dnkl/yambar/issues/17).
|
||||
([#17](https://codeberg.org/dnkl/yambar/issues/17)).
|
||||
* i3: `mode` tag: the name of the currently active mode
|
||||
|
||||
|
||||
|
@ -96,12 +581,12 @@
|
|||
error”_.
|
||||
* Memory leak when a YAML parsing error was encountered.
|
||||
* clock: update every second when necessary
|
||||
(https://codeberg.org/dnkl/yambar/issues/12).
|
||||
([#12](https://codeberg.org/dnkl/yambar/issues/12)).
|
||||
* mpd: fix compilation with clang
|
||||
(https://codeberg.org/dnkl/yambar/issues/16).
|
||||
([#16](https://codeberg.org/dnkl/yambar/issues/16)).
|
||||
* Crash when the alpha component in a color value was 0.
|
||||
* XCB: Fallback to non-primary monitor when the primary monitor is
|
||||
disconnected (https://codeberg.org/dnkl/yambar/issues/20)
|
||||
disconnected ([#20](https://codeberg.org/dnkl/yambar/issues/20))
|
||||
|
||||
|
||||
### Contributors
|
||||
|
|
7
PKGBUILD
7
PKGBUILD
|
@ -1,7 +1,8 @@
|
|||
pkgname=yambar
|
||||
pkgver=1.6.2
|
||||
pkgver=1.11.0
|
||||
pkgrel=1
|
||||
pkgdesc="Simplistic and highly configurable status panel for X and Wayland"
|
||||
changelog=CHANGELOG.md
|
||||
arch=('x86_64' 'aarch64')
|
||||
url=https://codeberg.org/dnkl/yambar
|
||||
license=(mit)
|
||||
|
@ -15,7 +16,9 @@ depends=(
|
|||
'libudev.so'
|
||||
'json-c'
|
||||
'libmpdclient'
|
||||
'fcft>=2.4.0')
|
||||
'libpulse'
|
||||
'pipewire'
|
||||
'fcft>=3.0.0' 'fcft<4.0.0')
|
||||
optdepends=('xcb-util-errors: better X error messages')
|
||||
source=()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pkgname=yambar-wayland
|
||||
pkgver=1.6.2
|
||||
pkgver=1.11.0
|
||||
pkgrel=1
|
||||
pkgdesc="Simplistic and highly configurable status panel for Wayland"
|
||||
arch=('x86_64' 'aarch64')
|
||||
|
@ -16,8 +16,11 @@ depends=(
|
|||
'libudev.so'
|
||||
'json-c'
|
||||
'libmpdclient'
|
||||
'fcft>=2.4.0')
|
||||
'libpulse'
|
||||
'pipewire'
|
||||
'fcft>=3.0.0' 'fcft<4.0.0')
|
||||
source=()
|
||||
changelog=CHANGELOG.md
|
||||
|
||||
pkgver() {
|
||||
cd ../.git &> /dev/null && git describe --tags --long | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' ||
|
||||
|
|
17
README.md
17
README.md
|
@ -1,6 +1,8 @@
|
|||
[](https://ci.codeberg.org/dnkl/yambar)
|
||||
|
||||
# Yambar
|
||||
|
||||
[](https://repology.org/project/yambar/versions)
|
||||
[](https://repology.org/project/yambar/versions)
|
||||
|
||||
|
||||
## Index
|
||||
|
@ -57,9 +59,9 @@ bar:
|
|||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{date}", right-margin: 5}
|
||||
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{time}"}
|
||||
```
|
||||
|
||||
|
@ -76,10 +78,17 @@ Available modules:
|
|||
* backlight
|
||||
* battery
|
||||
* clock
|
||||
* cpu
|
||||
* disk-io
|
||||
* dwl
|
||||
* foreign-toplevel
|
||||
* i3 (and Sway)
|
||||
* label
|
||||
* mem
|
||||
* mpd
|
||||
* network
|
||||
* pipewire
|
||||
* pulse
|
||||
* removables
|
||||
* river
|
||||
* script (see script [examples](examples/scripts))
|
||||
|
@ -98,7 +107,7 @@ mkdir -p bld/release && cd bld/release
|
|||
Second, configure the build (if you intend to install it globally, you
|
||||
might also want `--prefix=/usr`):
|
||||
```sh
|
||||
meson --buildtype=release ../..
|
||||
meson setup --buildtype=release ../..
|
||||
```
|
||||
|
||||
Optionally, explicitly disable a backend (or enable, if you want a
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
struct backend {
|
||||
bool (*setup)(struct bar *bar);
|
||||
void (*cleanup)(struct bar *bar);
|
||||
void (*loop)(struct bar *bar,
|
||||
void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event,
|
||||
enum mouse_button btn, int x, int y));
|
||||
void (*loop)(struct bar *bar, void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y));
|
||||
void (*commit)(const struct bar *bar);
|
||||
void (*refresh)(const struct bar *bar);
|
||||
void (*set_cursor)(struct bar *bar, const char *cursor);
|
||||
const char *(*output_name)(const struct bar *bar);
|
||||
};
|
||||
|
|
175
bar/bar.c
175
bar/bar.c
|
@ -1,15 +1,15 @@
|
|||
#include "bar.h"
|
||||
#include "private.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <threads.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
|
@ -18,15 +18,17 @@
|
|||
#include "../log.h"
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include "xcb.h"
|
||||
#include "xcb.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_WAYLAND)
|
||||
#include "wayland.h"
|
||||
#include "wayland.h"
|
||||
#endif
|
||||
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
/*
|
||||
* Calculate total width of left/center/rigth groups.
|
||||
* Calculate total width of left/center/right groups.
|
||||
* Note: begin_expose() must have been called
|
||||
*/
|
||||
static void
|
||||
|
@ -38,23 +40,33 @@ calculate_widths(const struct private *b, int *left, int *center, int *right)
|
|||
|
||||
for (size_t i = 0; i < b->left.count; i++) {
|
||||
struct exposable *e = b->left.exps[i];
|
||||
*left += b->left_spacing + e->width + b->right_spacing;
|
||||
if (e->width > 0)
|
||||
*left += b->left_spacing + e->width + b->right_spacing;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < b->center.count; i++) {
|
||||
struct exposable *e = b->center.exps[i];
|
||||
*center += b->left_spacing + e->width + b->right_spacing;
|
||||
if (e->width > 0)
|
||||
*center += b->left_spacing + e->width + b->right_spacing;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < b->right.count; i++) {
|
||||
struct exposable *e = b->right.exps[i];
|
||||
*right += b->left_spacing + e->width + b->right_spacing;
|
||||
if (e->width > 0)
|
||||
*right += b->left_spacing + e->width + b->right_spacing;
|
||||
}
|
||||
|
||||
/* No spacing on the edges (that's what the margins are for) */
|
||||
*left -= b->left_spacing + b->right_spacing;
|
||||
*center -= b->left_spacing + b->right_spacing;
|
||||
*right -= b->left_spacing + b->right_spacing;
|
||||
if (*left > 0)
|
||||
*left -= b->left_spacing + b->right_spacing;
|
||||
if (*center > 0)
|
||||
*center -= b->left_spacing + b->right_spacing;
|
||||
if (*right > 0)
|
||||
*right -= b->left_spacing + b->right_spacing;
|
||||
|
||||
assert(*left >= 0);
|
||||
assert(*center >= 0);
|
||||
assert(*right >= 0);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -63,20 +75,26 @@ expose(const struct bar *_bar)
|
|||
const struct private *bar = _bar->private;
|
||||
pixman_image_t *pix = bar->pix;
|
||||
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, pix, &bar->background, 1,
|
||||
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border});
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &bar->background, 1,
|
||||
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border});
|
||||
|
||||
if (bar->border.width > 0) {
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &bar->border.color, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
{0, 0, bar->width, bar->border.width},
|
||||
{0, 0, bar->border.width, bar->height_with_border},
|
||||
{bar->width - bar->border.width, 0, bar->border.width, bar->height_with_border},
|
||||
{0, bar->height_with_border - bar->border.width, bar->width, bar->border.width},
|
||||
});
|
||||
}
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &bar->border.color, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
/* Left */
|
||||
{0, 0, bar->border.left_width, bar->height_with_border},
|
||||
|
||||
/* Right */
|
||||
{bar->width - bar->border.right_width, 0, bar->border.right_width, bar->height_with_border},
|
||||
|
||||
/* Top */
|
||||
{bar->border.left_width, 0, bar->width - bar->border.left_width - bar->border.right_width,
|
||||
bar->border.top_width},
|
||||
|
||||
/* Bottom */
|
||||
{bar->border.left_width, bar->height_with_border - bar->border.bottom_width,
|
||||
bar->width - bar->border.left_width - bar->border.right_width, bar->border.bottom_width},
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
struct module *m = bar->left.mods[i];
|
||||
|
@ -84,6 +102,7 @@ expose(const struct bar *_bar)
|
|||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
bar->left.exps[i] = module_begin_expose(m);
|
||||
assert(bar->left.exps[i]->width >= 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
|
@ -92,6 +111,7 @@ expose(const struct bar *_bar)
|
|||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
bar->center.exps[i] = module_begin_expose(m);
|
||||
assert(bar->center.exps[i]->width >= 0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
|
@ -100,42 +120,49 @@ expose(const struct bar *_bar)
|
|||
if (e != NULL)
|
||||
e->destroy(e);
|
||||
bar->right.exps[i] = module_begin_expose(m);
|
||||
assert(bar->right.exps[i]->width >= 0);
|
||||
}
|
||||
|
||||
int left_width, center_width, right_width;
|
||||
calculate_widths(bar, &left_width, ¢er_width, &right_width);
|
||||
|
||||
int y = bar->border.width;
|
||||
int x = bar->border.width + bar->left_margin - bar->left_spacing;
|
||||
int y = bar->border.top_width;
|
||||
int x = bar->border.left_width + bar->left_margin - bar->left_spacing;
|
||||
pixman_region32_t clip;
|
||||
pixman_region32_init_rect(
|
||||
&clip, bar->border.left_width + bar->left_margin, bar->border.top_width,
|
||||
(bar->width - bar->left_margin - bar->right_margin - bar->border.left_width - bar->border.right_width),
|
||||
bar->height);
|
||||
pixman_image_set_clip_region32(pix, &clip);
|
||||
pixman_region32_fini(&clip);
|
||||
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
const struct exposable *e = bar->left.exps[i];
|
||||
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
if (e->width > 0)
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
x = bar->width / 2 - center_width / 2 - bar->left_spacing;
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
const struct exposable *e = bar->center.exps[i];
|
||||
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
if (e->width > 0)
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
x = bar->width - (
|
||||
right_width +
|
||||
bar->left_spacing +
|
||||
bar->right_margin +
|
||||
bar->border.width);
|
||||
x = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width);
|
||||
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
const struct exposable *e = bar->right.exps[i];
|
||||
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
if (e->width > 0)
|
||||
x += bar->left_spacing + e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
bar->backend.iface->commit(_bar);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
refresh(const struct bar *bar)
|
||||
{
|
||||
|
@ -150,16 +177,20 @@ set_cursor(struct bar *bar, const char *cursor)
|
|||
b->backend.iface->set_cursor(bar, cursor);
|
||||
}
|
||||
|
||||
static const char *
|
||||
output_name(const struct bar *bar)
|
||||
{
|
||||
const struct private *b = bar->private;
|
||||
return b->backend.iface->output_name(bar);
|
||||
}
|
||||
|
||||
static void
|
||||
on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn,
|
||||
int x, int y)
|
||||
on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x, int y)
|
||||
{
|
||||
struct private *bar = _bar->private;
|
||||
|
||||
if ((y < bar->border.width ||
|
||||
y >= (bar->height_with_border - bar->border.width)) ||
|
||||
(x < bar->border.width || x >= (bar->width - bar->border.width)))
|
||||
{
|
||||
if ((y < bar->border.top_width || y >= (bar->height_with_border - bar->border.bottom_width))
|
||||
|| (x < bar->border.left_width || x >= (bar->width - bar->border.right_width))) {
|
||||
set_cursor(_bar, "left_ptr");
|
||||
return;
|
||||
}
|
||||
|
@ -167,10 +198,13 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn,
|
|||
int left_width, center_width, right_width;
|
||||
calculate_widths(bar, &left_width, ¢er_width, &right_width);
|
||||
|
||||
int mx = bar->border.width + bar->left_margin - bar->left_spacing;
|
||||
int mx = bar->border.left_width + bar->left_margin - bar->left_spacing;
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
struct exposable *e = bar->left.exps[i];
|
||||
|
||||
if (e->width == 0)
|
||||
continue;
|
||||
|
||||
mx += bar->left_spacing;
|
||||
if (x >= mx && x < mx + e->width) {
|
||||
if (e->on_mouse != NULL)
|
||||
|
@ -185,6 +219,9 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn,
|
|||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
struct exposable *e = bar->center.exps[i];
|
||||
|
||||
if (e->width == 0)
|
||||
continue;
|
||||
|
||||
mx += bar->left_spacing;
|
||||
if (x >= mx && x < mx + e->width) {
|
||||
if (e->on_mouse != NULL)
|
||||
|
@ -195,14 +232,14 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn,
|
|||
mx += e->width + bar->right_spacing;
|
||||
}
|
||||
|
||||
mx = bar->width - (right_width
|
||||
+ bar->left_spacing +
|
||||
bar->right_margin +
|
||||
bar->border.width);
|
||||
mx = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width);
|
||||
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
struct exposable *e = bar->right.exps[i];
|
||||
|
||||
if (e->width == 0)
|
||||
continue;
|
||||
|
||||
mx += bar->left_spacing;
|
||||
if (x >= mx && x < mx + e->width) {
|
||||
if (e->on_mouse != NULL)
|
||||
|
@ -236,7 +273,7 @@ run(struct bar *_bar)
|
|||
{
|
||||
struct private *bar = _bar->private;
|
||||
|
||||
bar->height_with_border = bar->height + 2 * bar->border.width;
|
||||
bar->height_with_border = bar->height + bar->border.top_width + bar->border.bottom_width;
|
||||
|
||||
if (!bar->backend.iface->setup(_bar)) {
|
||||
bar->backend.iface->cleanup(_bar);
|
||||
|
@ -246,11 +283,12 @@ run(struct bar *_bar)
|
|||
}
|
||||
|
||||
set_cursor(_bar, "left_ptr");
|
||||
expose(_bar);
|
||||
|
||||
/* Start modules */
|
||||
thrd_t thrd_left[bar->left.count];
|
||||
thrd_t thrd_center[bar->center.count];
|
||||
thrd_t thrd_right[bar->right.count];
|
||||
thrd_t thrd_left[max(bar->left.count, 1)];
|
||||
thrd_t thrd_center[max(bar->center.count, 1)];
|
||||
thrd_t thrd_right[max(bar->right.count, 1)];
|
||||
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
struct module *mod = bar->left.mods[i];
|
||||
|
@ -285,20 +323,26 @@ run(struct bar *_bar)
|
|||
int mod_ret;
|
||||
for (size_t i = 0; i < bar->left.count; i++) {
|
||||
thrd_join(thrd_left[i], &mod_ret);
|
||||
if (mod_ret != 0)
|
||||
LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, mod_ret);
|
||||
if (mod_ret != 0) {
|
||||
const struct module *m = bar->left.mods[i];
|
||||
LOG_ERR("module: LEFT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
|
||||
}
|
||||
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
|
||||
}
|
||||
for (size_t i = 0; i < bar->center.count; i++) {
|
||||
thrd_join(thrd_center[i], &mod_ret);
|
||||
if (mod_ret != 0)
|
||||
LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, mod_ret);
|
||||
if (mod_ret != 0) {
|
||||
const struct module *m = bar->center.mods[i];
|
||||
LOG_ERR("module: CENTER #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
|
||||
}
|
||||
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
|
||||
}
|
||||
for (size_t i = 0; i < bar->right.count; i++) {
|
||||
thrd_join(thrd_right[i], &mod_ret);
|
||||
if (mod_ret != 0)
|
||||
LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret);
|
||||
if (mod_ret != 0) {
|
||||
const struct module *m = bar->right.mods[i];
|
||||
LOG_ERR("module: RIGHT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
|
||||
}
|
||||
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
|
||||
}
|
||||
|
||||
|
@ -386,7 +430,7 @@ bar_new(const struct bar_config *config)
|
|||
break;
|
||||
|
||||
case BAR_BACKEND_WAYLAND:
|
||||
#if defined(BAR_WAYLAND)
|
||||
#if defined(ENABLE_WAYLAND)
|
||||
backend_data = bar_backend_wayland_new();
|
||||
backend_iface = &wayland_backend_iface;
|
||||
#else
|
||||
|
@ -401,6 +445,7 @@ bar_new(const struct bar_config *config)
|
|||
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->monitor = config->monitor != NULL ? strdup(config->monitor) : NULL;
|
||||
priv->layer = config->layer;
|
||||
priv->location = config->location;
|
||||
priv->height = config->height;
|
||||
priv->background = config->background;
|
||||
|
@ -409,7 +454,10 @@ bar_new(const struct bar_config *config)
|
|||
priv->left_margin = config->left_margin;
|
||||
priv->right_margin = config->right_margin;
|
||||
priv->trackpad_sensitivity = config->trackpad_sensitivity;
|
||||
priv->border.width = config->border.width;
|
||||
priv->border.left_width = config->border.left_width;
|
||||
priv->border.right_width = config->border.right_width;
|
||||
priv->border.top_width = config->border.top_width;
|
||||
priv->border.bottom_width = config->border.bottom_width;
|
||||
priv->border.color = config->border.color;
|
||||
priv->border.left_margin = config->border.left_margin;
|
||||
priv->border.right_margin = config->border.right_margin;
|
||||
|
@ -440,6 +488,7 @@ bar_new(const struct bar_config *config)
|
|||
bar->destroy = &destroy;
|
||||
bar->refresh = &refresh;
|
||||
bar->set_cursor = &set_cursor;
|
||||
bar->output_name = &output_name;
|
||||
|
||||
for (size_t i = 0; i < priv->left.count; i++)
|
||||
priv->left.mods[i]->bar = bar;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../color.h"
|
||||
#include "../font-shaping.h"
|
||||
#include "../module.h"
|
||||
|
||||
struct bar {
|
||||
|
@ -12,16 +13,21 @@ struct bar {
|
|||
|
||||
void (*refresh)(const struct bar *bar);
|
||||
void (*set_cursor)(struct bar *bar, const char *cursor);
|
||||
|
||||
const char *(*output_name)(const struct bar *bar);
|
||||
};
|
||||
|
||||
enum bar_location { BAR_TOP, BAR_BOTTOM };
|
||||
enum bar_layer { BAR_LAYER_OVERLAY, BAR_LAYER_TOP, BAR_LAYER_BOTTOM, BAR_LAYER_BACKGROUND };
|
||||
enum bar_backend { BAR_BACKEND_AUTO, BAR_BACKEND_XCB, BAR_BACKEND_WAYLAND };
|
||||
|
||||
struct bar_config {
|
||||
enum bar_backend backend;
|
||||
|
||||
const char *monitor;
|
||||
enum bar_layer layer;
|
||||
enum bar_location location;
|
||||
enum font_shaping font_shaping;
|
||||
int height;
|
||||
int left_spacing, right_spacing;
|
||||
int left_margin, right_margin;
|
||||
|
@ -30,7 +36,8 @@ struct bar_config {
|
|||
pixman_color_t background;
|
||||
|
||||
struct {
|
||||
int width;
|
||||
int left_width, right_width;
|
||||
int top_width, bottom_width;
|
||||
pixman_color_t color;
|
||||
int left_margin, right_margin;
|
||||
int top_margin, bottom_margin;
|
||||
|
|
|
@ -7,11 +7,11 @@ endif
|
|||
|
||||
if backend_wayland
|
||||
wayland_protocols = dependency('wayland-protocols')
|
||||
wayland_protocols_datadir = wayland_protocols.get_pkgconfig_variable('pkgdatadir')
|
||||
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
|
||||
|
||||
wscanner = dependency('wayland-scanner', native: true)
|
||||
wscanner_prog = find_program(
|
||||
wscanner.get_pkgconfig_variable('wayland_scanner'), native: true)
|
||||
wscanner.get_variable('wayland_scanner'), native: true)
|
||||
|
||||
wl_proto_headers = []
|
||||
wl_proto_src = []
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
#include "../bar/bar.h"
|
||||
#include "backend.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
/* From bar_config */
|
||||
char *monitor;
|
||||
enum bar_layer layer;
|
||||
enum bar_location location;
|
||||
int height;
|
||||
int left_spacing, right_spacing;
|
||||
|
@ -15,7 +17,8 @@ struct private {
|
|||
pixman_color_t background;
|
||||
|
||||
struct {
|
||||
int width;
|
||||
int left_width, right_width;
|
||||
int top_width, bottom_width;
|
||||
pixman_color_t color;
|
||||
int left_margin, right_margin;
|
||||
int top_margin, bottom_margin;
|
||||
|
|
726
bar/wayland.c
726
bar/wayland.c
File diff suppressed because it is too large
Load diff
222
bar/xcb.c
222
bar/xcb.c
|
@ -1,16 +1,16 @@
|
|||
#include "xcb.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pixman.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/randr.h>
|
||||
#include <xcb/render.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xcb_cursor.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
|
@ -39,7 +39,6 @@ struct xcb_backend {
|
|||
void *client_pixmap;
|
||||
size_t client_pixmap_size;
|
||||
pixman_image_t *pix;
|
||||
|
||||
};
|
||||
|
||||
void *
|
||||
|
@ -55,11 +54,8 @@ setup(struct bar *_bar)
|
|||
struct private *bar = _bar->private;
|
||||
struct xcb_backend *backend = bar->backend.data;
|
||||
|
||||
if (bar->border.left_margin != 0 ||
|
||||
bar->border.right_margin != 0 ||
|
||||
bar->border.top_margin != 0 ||
|
||||
bar->border.bottom_margin)
|
||||
{
|
||||
if (bar->border.left_margin != 0 || bar->border.right_margin != 0 || bar->border.top_margin != 0
|
||||
|| bar->border.bottom_margin) {
|
||||
LOG_WARN("non-zero border margins ignored in X11 backend");
|
||||
}
|
||||
|
||||
|
@ -76,10 +72,8 @@ setup(struct bar *_bar)
|
|||
|
||||
xcb_screen_t *screen = xcb_aux_get_screen(backend->conn, default_screen);
|
||||
|
||||
xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply(
|
||||
backend->conn,
|
||||
xcb_randr_get_monitors(backend->conn, screen->root, 0),
|
||||
&e);
|
||||
xcb_randr_get_monitors_reply_t *monitors
|
||||
= xcb_randr_get_monitors_reply(backend->conn, xcb_randr_get_monitors(backend->conn, screen->root, 0), &e);
|
||||
|
||||
if (e != NULL) {
|
||||
LOG_ERR("failed to get monitor list: %s", xcb_error(e));
|
||||
|
@ -90,17 +84,13 @@ setup(struct bar *_bar)
|
|||
|
||||
/* Find monitor coordinates and width/height */
|
||||
bool found_monitor = false;
|
||||
for (xcb_randr_monitor_info_iterator_t it =
|
||||
xcb_randr_get_monitors_monitors_iterator(monitors);
|
||||
it.rem > 0;
|
||||
xcb_randr_monitor_info_next(&it))
|
||||
{
|
||||
for (xcb_randr_monitor_info_iterator_t it = xcb_randr_get_monitors_monitors_iterator(monitors); it.rem > 0;
|
||||
xcb_randr_monitor_info_next(&it)) {
|
||||
const xcb_randr_monitor_info_t *mon = it.data;
|
||||
char *name = get_atom_name(backend->conn, mon->name);
|
||||
|
||||
LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name,
|
||||
mon->width, mon->height, mon->x, mon->y,
|
||||
mon->width_in_millimeters, mon->height_in_millimeters);
|
||||
LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name, mon->width, mon->height, mon->x, mon->y,
|
||||
mon->width_in_millimeters, mon->height_in_millimeters);
|
||||
|
||||
/* User wants a specific monitor, and this is not the one */
|
||||
if (bar->monitor != NULL && strcmp(bar->monitor, name) != 0) {
|
||||
|
@ -111,14 +101,11 @@ setup(struct bar *_bar)
|
|||
backend->x = mon->x;
|
||||
backend->y = mon->y;
|
||||
bar->width = mon->width;
|
||||
backend->y += bar->location == BAR_TOP ? 0
|
||||
: screen->height_in_pixels - bar->height_with_border;
|
||||
backend->y += bar->location == BAR_TOP ? 0 : mon->height - bar->height_with_border;
|
||||
|
||||
found_monitor = true;
|
||||
|
||||
if ((bar->monitor != NULL && strcmp(bar->monitor, name) == 0) ||
|
||||
(bar->monitor == NULL && mon->primary))
|
||||
{
|
||||
if ((bar->monitor != NULL && strcmp(bar->monitor, name) == 0) || (bar->monitor == NULL && mon->primary)) {
|
||||
/* Exact match */
|
||||
free(name);
|
||||
break;
|
||||
|
@ -155,74 +142,47 @@ setup(struct bar *_bar)
|
|||
LOG_DBG("using a %hhu-bit visual", depth);
|
||||
|
||||
backend->colormap = xcb_generate_id(backend->conn);
|
||||
xcb_create_colormap(
|
||||
backend->conn, 0, backend->colormap, screen->root, vis->visual_id);
|
||||
xcb_create_colormap(backend->conn, 0, backend->colormap, screen->root, vis->visual_id);
|
||||
|
||||
backend->win = xcb_generate_id(backend->conn);
|
||||
xcb_create_window(
|
||||
backend->conn,
|
||||
depth, backend->win, screen->root,
|
||||
backend->x, backend->y, bar->width, bar->height_with_border,
|
||||
0,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
|
||||
(XCB_CW_BACK_PIXEL |
|
||||
XCB_CW_BORDER_PIXEL |
|
||||
XCB_CW_EVENT_MASK |
|
||||
XCB_CW_COLORMAP),
|
||||
(const uint32_t []){
|
||||
screen->black_pixel,
|
||||
screen->white_pixel,
|
||||
(XCB_EVENT_MASK_EXPOSURE |
|
||||
XCB_EVENT_MASK_BUTTON_RELEASE |
|
||||
XCB_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_EVENT_MASK_POINTER_MOTION |
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY),
|
||||
backend->colormap}
|
||||
);
|
||||
backend->conn, depth, backend->win, screen->root, backend->x, backend->y, bar->width, bar->height_with_border,
|
||||
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
|
||||
(XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP),
|
||||
(const uint32_t[]){screen->black_pixel, screen->white_pixel,
|
||||
(XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS
|
||||
| XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_STRUCTURE_NOTIFY),
|
||||
backend->colormap});
|
||||
|
||||
const char *title = "yambar";
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
||||
strlen(title), title);
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
||||
strlen(title), title);
|
||||
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){getpid()});
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32,
|
||||
1, (const uint32_t []){_NET_WM_WINDOW_TYPE_DOCK});
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32,
|
||||
2, (const uint32_t []){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){0xffffffff});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1,
|
||||
(const uint32_t[]){getpid()});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 1,
|
||||
(const uint32_t[]){_NET_WM_WINDOW_TYPE_DOCK});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STATE, XCB_ATOM_ATOM, 32, 2,
|
||||
(const uint32_t[]){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1,
|
||||
(const uint32_t[]){0xffffffff});
|
||||
|
||||
/* Always on top */
|
||||
xcb_configure_window(
|
||||
backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE,
|
||||
(const uint32_t []){XCB_STACK_MODE_ABOVE});
|
||||
xcb_configure_window(backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE,
|
||||
(const uint32_t[]){XCB_STACK_MODE_ABOVE});
|
||||
|
||||
uint32_t top_strut, bottom_strut;
|
||||
uint32_t top_pair[2], bottom_pair[2];
|
||||
|
||||
if (bar->location == BAR_TOP) {
|
||||
top_strut = backend->y + bar->height_with_border;
|
||||
top_strut = bar->height_with_border;
|
||||
top_pair[0] = backend->x;
|
||||
top_pair[1] = backend->x + bar->width - 1;
|
||||
|
||||
bottom_strut = 0;
|
||||
bottom_pair[0] = bottom_pair[1] = 0;
|
||||
} else {
|
||||
bottom_strut = screen->height_in_pixels - backend->y;
|
||||
bottom_strut = bar->height_with_border;
|
||||
bottom_pair[0] = backend->x;
|
||||
bottom_pair[1] = backend->x + bar->width - 1;
|
||||
|
||||
|
@ -232,42 +192,38 @@ setup(struct bar *_bar)
|
|||
|
||||
uint32_t strut[] = {
|
||||
/* left/right/top/bottom */
|
||||
0, 0,
|
||||
0,
|
||||
0,
|
||||
top_strut,
|
||||
bottom_strut,
|
||||
|
||||
/* start/end pairs for left/right/top/bottom */
|
||||
0, 0,
|
||||
0, 0,
|
||||
top_pair[0], top_pair[1],
|
||||
bottom_pair[0], bottom_pair[1],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
top_pair[0],
|
||||
top_pair[1],
|
||||
bottom_pair[0],
|
||||
bottom_pair[1],
|
||||
};
|
||||
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_STRUT, XCB_ATOM_CARDINAL, 32,
|
||||
4, strut);
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4,
|
||||
strut);
|
||||
|
||||
xcb_change_property(
|
||||
backend->conn,
|
||||
XCB_PROP_MODE_REPLACE, backend->win,
|
||||
_NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32,
|
||||
12, strut);
|
||||
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL,
|
||||
32, 12, strut);
|
||||
|
||||
backend->gc = xcb_generate_id(backend->conn);
|
||||
xcb_create_gc(backend->conn, backend->gc, backend->win,
|
||||
XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
|
||||
(const uint32_t []){screen->white_pixel, 0});
|
||||
xcb_create_gc(backend->conn, backend->gc, backend->win, XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
|
||||
(const uint32_t[]){screen->white_pixel, 0});
|
||||
|
||||
const uint32_t stride = stride_for_format_and_width(
|
||||
PIXMAN_a8r8g8b8, bar->width);
|
||||
const uint32_t stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, bar->width);
|
||||
|
||||
backend->client_pixmap_size = stride * bar->height_with_border;
|
||||
backend->client_pixmap = malloc(backend->client_pixmap_size);
|
||||
backend->pix = pixman_image_create_bits_no_clear(
|
||||
PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
|
||||
(uint32_t *)backend->client_pixmap, stride);
|
||||
backend->pix = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
|
||||
(uint32_t *)backend->client_pixmap, stride);
|
||||
bar->pix = backend->pix;
|
||||
|
||||
xcb_map_window(backend->conn, backend->win);
|
||||
|
@ -310,10 +266,8 @@ cleanup(struct bar *_bar)
|
|||
}
|
||||
|
||||
static void
|
||||
loop(struct bar *_bar,
|
||||
void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event,
|
||||
enum mouse_button btn, int x, int y))
|
||||
loop(struct bar *_bar, void (*expose)(const struct bar *bar),
|
||||
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y))
|
||||
{
|
||||
struct private *bar = _bar->private;
|
||||
struct xcb_backend *backend = bar->backend.data;
|
||||
|
@ -323,10 +277,7 @@ loop(struct bar *_bar,
|
|||
const int fd = xcb_get_file_descriptor(backend->conn);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = _bar->abort_fd, .events = POLLIN},
|
||||
{.fd = fd, .events = POLLIN}
|
||||
};
|
||||
struct pollfd fds[] = {{.fd = _bar->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}};
|
||||
|
||||
poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
|
||||
|
@ -335,18 +286,14 @@ loop(struct bar *_bar,
|
|||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_WARN("disconnected from XCB");
|
||||
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
LOG_ERRNO("failed to signal abort to modules");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn);
|
||||
e != NULL;
|
||||
e = xcb_poll_for_event(backend->conn))
|
||||
{
|
||||
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn); e != NULL;
|
||||
e = xcb_poll_for_event(backend->conn)) {
|
||||
switch (XCB_EVENT_RESPONSE_TYPE(e)) {
|
||||
case 0:
|
||||
LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e));
|
||||
|
@ -369,9 +316,12 @@ loop(struct bar *_bar,
|
|||
const xcb_button_release_event_t *evt = (void *)e;
|
||||
|
||||
switch (evt->detail) {
|
||||
case 1: case 2: case 3: case 4: case 5:
|
||||
on_mouse(_bar, ON_MOUSE_CLICK,
|
||||
evt->detail, evt->event_x, evt->event_y);
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
on_mouse(_bar, ON_MOUSE_CLICK, evt->detail, evt->event_x, evt->event_y);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -405,10 +355,9 @@ commit(const struct bar *_bar)
|
|||
const struct private *bar = _bar->private;
|
||||
const struct xcb_backend *backend = bar->backend.data;
|
||||
|
||||
xcb_put_image(
|
||||
backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc,
|
||||
bar->width, bar->height_with_border, 0, 0, 0,
|
||||
backend->depth, backend->client_pixmap_size, backend->client_pixmap);
|
||||
xcb_put_image(backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc, bar->width,
|
||||
bar->height_with_border, 0, 0, 0, backend->depth, backend->client_pixmap_size,
|
||||
backend->client_pixmap);
|
||||
xcb_flush(backend->conn);
|
||||
}
|
||||
|
||||
|
@ -420,23 +369,19 @@ refresh(const struct bar *_bar)
|
|||
|
||||
/* Send an event to handle refresh from main thread */
|
||||
|
||||
/* Note: docs say that all X11 events are 32 bytes, reglardless of
|
||||
/* Note: docs say that all X11 events are 32 bytes, regardless of
|
||||
* the size of the event structure */
|
||||
xcb_expose_event_t *evt = calloc(32, 1);
|
||||
|
||||
*evt = (xcb_expose_event_t){
|
||||
.response_type = XCB_EXPOSE,
|
||||
.window = backend->win,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = bar->width,
|
||||
.height = bar->height,
|
||||
.count = 1
|
||||
};
|
||||
*evt = (xcb_expose_event_t){.response_type = XCB_EXPOSE,
|
||||
.window = backend->win,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = bar->width,
|
||||
.height = bar->height,
|
||||
.count = 1};
|
||||
|
||||
xcb_send_event(
|
||||
backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE,
|
||||
(char *)evt);
|
||||
xcb_send_event(backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE, (char *)evt);
|
||||
|
||||
xcb_flush(backend->conn);
|
||||
free(evt);
|
||||
|
@ -458,8 +403,14 @@ set_cursor(struct bar *_bar, const char *cursor)
|
|||
xcb_free_cursor(backend->conn, backend->cursor);
|
||||
|
||||
backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor);
|
||||
xcb_change_window_attributes(
|
||||
backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
|
||||
xcb_change_window_attributes(backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
|
||||
}
|
||||
|
||||
static const char *
|
||||
output_name(const struct bar *_bar)
|
||||
{
|
||||
/* Not implemented */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct backend xcb_backend_iface = {
|
||||
|
@ -469,4 +420,5 @@ const struct backend xcb_backend_iface = {
|
|||
.commit = &commit,
|
||||
.refresh = &refresh,
|
||||
.set_cursor = &set_cursor,
|
||||
.output_name = &output_name,
|
||||
};
|
||||
|
|
83
char32.c
Normal file
83
char32.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "char32.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#if defined __has_include
|
||||
#if __has_include(<stdc-predef.h>)
|
||||
#include <stdc-predef.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define LOG_MODULE "char32"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* For now, assume we can map directly to the corresponding wchar_t
|
||||
* functions. This is true if:
|
||||
*
|
||||
* - both data types have the same size
|
||||
* - both use the same encoding (though we require that encoding to be UTF-32)
|
||||
*/
|
||||
|
||||
_Static_assert(sizeof(wchar_t) == sizeof(char32_t), "wchar_t vs. char32_t size mismatch");
|
||||
|
||||
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||
#error "char32_t does not use UTF-32"
|
||||
#endif
|
||||
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__)
|
||||
#error "wchar_t does not use UTF-32"
|
||||
#endif
|
||||
|
||||
size_t
|
||||
c32len(const char32_t *s)
|
||||
{
|
||||
return wcslen((const wchar_t *)s);
|
||||
}
|
||||
|
||||
char32_t *
|
||||
ambstoc32(const char *src)
|
||||
{
|
||||
if (src == NULL)
|
||||
return NULL;
|
||||
|
||||
const size_t src_len = strlen(src);
|
||||
|
||||
char32_t *ret = malloc((src_len + 1) * sizeof(ret[0]));
|
||||
if (ret == NULL)
|
||||
return NULL;
|
||||
|
||||
mbstate_t ps = {0};
|
||||
char32_t *out = ret;
|
||||
const char *in = src;
|
||||
const char *const end = src + src_len + 1;
|
||||
|
||||
size_t chars = 0;
|
||||
size_t rc;
|
||||
|
||||
while ((rc = mbrtoc32(out, in, end - in, &ps)) != 0) {
|
||||
switch (rc) {
|
||||
case (size_t)-1:
|
||||
case (size_t)-2:
|
||||
case (size_t)-3:
|
||||
goto err;
|
||||
}
|
||||
|
||||
in += rc;
|
||||
out++;
|
||||
chars++;
|
||||
}
|
||||
|
||||
*out = U'\0';
|
||||
|
||||
ret = realloc(ret, (chars + 1) * sizeof(ret[0]));
|
||||
return ret;
|
||||
|
||||
err:
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
7
char32.h
Normal file
7
char32.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <uchar.h>
|
||||
|
||||
size_t c32len(const char32_t *s);
|
||||
char32_t *ambstoc32(const char *src);
|
|
@ -8,5 +8,6 @@ _arguments \
|
|||
'(-c --config)'{-c,--config}'[alternative configuration file]:filename:_files' \
|
||||
'(-C --validate)'{-C,--validate}'[verify configuration then quit]' \
|
||||
'(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running]:pidfile:_files' \
|
||||
'(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \
|
||||
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \
|
||||
'(-s --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging]'
|
||||
|
|
166
config-verify.c
166
config-verify.c
|
@ -1,7 +1,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <tllist.h>
|
||||
|
||||
|
@ -16,11 +16,9 @@ conf_err_prefix(const keychain_t *chain, const struct yml_node *node)
|
|||
static char msg[4096];
|
||||
int idx = 0;
|
||||
|
||||
idx += snprintf(&msg[idx], sizeof(msg) - idx, "%zu:%zu: ",
|
||||
yml_source_line(node), yml_source_column(node));
|
||||
idx += snprintf(&msg[idx], sizeof(msg) - idx, "%zu:%zu: ", yml_source_line(node), yml_source_column(node));
|
||||
|
||||
tll_foreach(*chain, key)
|
||||
idx += snprintf(&msg[idx], sizeof(msg) - idx, "%s.", key->item);
|
||||
tll_foreach(*chain, key) idx += snprintf(&msg[idx], sizeof(msg) - idx, "%s.", key->item);
|
||||
|
||||
/* Remove trailing "." */
|
||||
msg[idx - 1] = '\0';
|
||||
|
@ -45,8 +43,27 @@ conf_verify_int(keychain_t *chain, const struct yml_node *node)
|
|||
if (yml_value_is_int(node))
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not an integer: '%s'",
|
||||
conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
LOG_ERR("%s: value is not an integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_unsigned(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (yml_value_is_int(node) && yml_value_as_int(node) >= 0)
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not a positive integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_bool(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (yml_value_is_bool(node))
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not a boolean: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -59,10 +76,7 @@ conf_verify_list(keychain_t *chain, const struct yml_node *node,
|
|||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_list_iter iter = yml_list_iter(node);
|
||||
iter.node != NULL;
|
||||
yml_list_next(&iter))
|
||||
{
|
||||
for (struct yml_list_iter iter = yml_list_iter(node); iter.node != NULL; yml_list_next(&iter)) {
|
||||
if (!verify(chain, iter.node))
|
||||
return false;
|
||||
}
|
||||
|
@ -71,8 +85,7 @@ conf_verify_list(keychain_t *chain, const struct yml_node *node,
|
|||
}
|
||||
|
||||
bool
|
||||
conf_verify_enum(keychain_t *chain, const struct yml_node *node,
|
||||
const char *values[], size_t count)
|
||||
conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count)
|
||||
{
|
||||
const char *s = yml_value_as_string(node);
|
||||
if (s == NULL) {
|
||||
|
@ -93,8 +106,7 @@ conf_verify_enum(keychain_t *chain, const struct yml_node *node,
|
|||
}
|
||||
|
||||
bool
|
||||
conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
||||
const struct attr_info info[])
|
||||
conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct attr_info info[])
|
||||
{
|
||||
if (!yml_is_dict(node)) {
|
||||
LOG_ERR("%s: must be a dictionary", conf_err_prefix(chain, node));
|
||||
|
@ -109,10 +121,7 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
|||
bool exists[count];
|
||||
memset(exists, 0, sizeof(exists));
|
||||
|
||||
for (struct yml_dict_iter it = yml_dict_iter(node);
|
||||
it.key != NULL;
|
||||
yml_dict_next(&it))
|
||||
{
|
||||
for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) {
|
||||
const char *key = yml_value_as_string(it.key);
|
||||
if (key == NULL) {
|
||||
LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key));
|
||||
|
@ -152,21 +161,43 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_on_click_path(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_string(chain, node))
|
||||
return false;
|
||||
|
||||
#if 1
|
||||
/* We allow non-absolute paths in on-click handlers */
|
||||
return true;
|
||||
#else
|
||||
const char *path = yml_value_as_string(node);
|
||||
|
||||
const bool is_absolute = path[0] == '/';
|
||||
const bool is_tilde = path[0] == '~' && path[1] == '/';
|
||||
|
||||
if (!is_absolute && !is_tilde) {
|
||||
LOG_ERR("%s: path must be either absolute, or begin with '~/", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_on_click(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
/* on-click: <command> */
|
||||
const char *s = yml_value_as_string(node);
|
||||
if (s != NULL)
|
||||
return true;
|
||||
return verify_on_click_path(chain, node);
|
||||
|
||||
static const struct attr_info info[] = {
|
||||
{"left", false, &conf_verify_string},
|
||||
{"middle", false, &conf_verify_string},
|
||||
{"right", false, &conf_verify_string},
|
||||
{"wheel-up", false, &conf_verify_string},
|
||||
{"wheel-down", false, &conf_verify_string},
|
||||
{NULL, false, NULL},
|
||||
{"left", false, &verify_on_click_path}, {"middle", false, &verify_on_click_path},
|
||||
{"right", false, &verify_on_click_path}, {"wheel-up", false, &verify_on_click_path},
|
||||
{"wheel-down", false, &verify_on_click_path}, {"previous", false, &verify_on_click_path},
|
||||
{"next", false, &verify_on_click_path}, {NULL, false, NULL},
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, info);
|
||||
|
@ -185,27 +216,30 @@ conf_verify_color(keychain_t *chain, const struct yml_node *node)
|
|||
int v = sscanf(s, "%02x%02x%02x%02x", &r, &g, &b, &a);
|
||||
|
||||
if (strlen(s) != 8 || v != 4) {
|
||||
LOG_ERR("%s: value must be a color ('rrggbbaa', e.g ff00ffff)",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: value must be a color ('rrggbbaa', e.g ff00ffff)", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
conf_verify_font(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_scalar(node)) {
|
||||
LOG_ERR("%s: font must be a fontconfig-formatted string",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: font must be a fontconfig-formatted string", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_enum(chain, node, (const char *[]){"full", /*"graphemes",*/ "none"}, 2);
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
|
@ -213,7 +247,8 @@ conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
if (yml_dict_length(node) != 1) {
|
||||
LOG_ERR("%s: decoration must be a dictionary with a single key; "
|
||||
"the name of the particle", conf_err_prefix(chain, node));
|
||||
"the name of the particle",
|
||||
conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -229,8 +264,7 @@ conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
const struct deco_iface *iface = plugin_load_deco(deco_name);
|
||||
if (iface == NULL) {
|
||||
LOG_ERR("%s: invalid decoration name: %s",
|
||||
conf_err_prefix(chain, deco), deco_name);
|
||||
LOG_ERR("%s: invalid decoration name: %s", conf_err_prefix(chain, deco), deco_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -247,10 +281,7 @@ conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
assert(yml_is_list(node));
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it))
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
|
||||
if (!conf_verify_particle(chain, it.node))
|
||||
return false;
|
||||
}
|
||||
|
@ -265,7 +296,8 @@ conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
if (yml_dict_length(node) != 1) {
|
||||
LOG_ERR("%s: particle must be a dictionary with a single key; "
|
||||
"the name of the particle", conf_err_prefix(chain, node));
|
||||
"the name of the particle",
|
||||
conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -281,8 +313,7 @@ conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
const struct particle_iface *iface = plugin_load_particle(particle_name);
|
||||
if (iface == NULL) {
|
||||
LOG_ERR("%s: invalid particle name: %s",
|
||||
conf_err_prefix(chain, particle), particle_name);
|
||||
LOG_ERR("%s: invalid particle name: %s", conf_err_prefix(chain, particle), particle_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -304,19 +335,18 @@ conf_verify_particle(keychain_t *chain, const struct yml_node *node)
|
|||
else if (yml_is_list(node))
|
||||
return conf_verify_particle_list_items(chain, node);
|
||||
else {
|
||||
LOG_ERR("%s: particle must be either a dictionary or a list",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: particle must be either a dictionary or a list", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
verify_module(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_dict(node) || yml_dict_length(node) != 1) {
|
||||
LOG_ERR("%s: module must be a dictionary with a single key; "
|
||||
"the name of the module", conf_err_prefix(chain, node));
|
||||
"the name of the module",
|
||||
conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -332,8 +362,7 @@ verify_module(keychain_t *chain, const struct yml_node *node)
|
|||
|
||||
const struct module_iface *iface = plugin_load_module(mod_name);
|
||||
if (iface == NULL) {
|
||||
LOG_ERR(
|
||||
"%s: invalid module name: %s", conf_err_prefix(chain, node), mod_name);
|
||||
LOG_ERR("%s: invalid module name: %s", conf_err_prefix(chain, node), mod_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -355,10 +384,7 @@ verify_module_list(keychain_t *chain, const struct yml_node *node)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it))
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
|
||||
if (!verify_module(chain, it.node))
|
||||
return false;
|
||||
}
|
||||
|
@ -370,14 +396,12 @@ static bool
|
|||
verify_bar_border(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"width", false, &conf_verify_int},
|
||||
{"color", false, &conf_verify_color},
|
||||
{"margin", false, &conf_verify_int},
|
||||
{"left-margin", false, &conf_verify_int},
|
||||
{"right-margin", false, &conf_verify_int},
|
||||
{"top-margin", false, &conf_verify_int},
|
||||
{"bottom-margin", false, &conf_verify_int},
|
||||
{NULL, false, NULL},
|
||||
{"width", false, &conf_verify_unsigned}, {"left-width", false, &conf_verify_unsigned},
|
||||
{"right-width", false, &conf_verify_unsigned}, {"top-width", false, &conf_verify_unsigned},
|
||||
{"bottom-width", false, &conf_verify_unsigned}, {"color", false, &conf_verify_color},
|
||||
{"margin", false, &conf_verify_unsigned}, {"left-margin", false, &conf_verify_unsigned},
|
||||
{"right-margin", false, &conf_verify_unsigned}, {"top-margin", false, &conf_verify_unsigned},
|
||||
{"bottom-margin", false, &conf_verify_unsigned}, {NULL, false, NULL},
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
|
@ -389,6 +413,12 @@ verify_bar_location(keychain_t *chain, const struct yml_node *node)
|
|||
return conf_verify_enum(chain, node, (const char *[]){"top", "bottom"}, 2);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_bar_layer(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_enum(chain, node, (const char *[]){"overlay", "top", "bottom", "background"}, 4);
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_bar(const struct yml_node *bar)
|
||||
{
|
||||
|
@ -401,29 +431,31 @@ conf_verify_bar(const struct yml_node *bar)
|
|||
chain_push(&chain, "bar");
|
||||
|
||||
static const struct attr_info attrs[] = {
|
||||
{"height", true, &conf_verify_int},
|
||||
{"height", true, &conf_verify_unsigned},
|
||||
{"location", true, &verify_bar_location},
|
||||
{"background", true, &conf_verify_color},
|
||||
|
||||
{"monitor", false, &conf_verify_string},
|
||||
{"layer", false, &verify_bar_layer},
|
||||
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
|
||||
{"margin", false, &conf_verify_int},
|
||||
{"left_margin", false, &conf_verify_int},
|
||||
{"right_margin", false, &conf_verify_int},
|
||||
{"margin", false, &conf_verify_unsigned},
|
||||
{"left-margin", false, &conf_verify_unsigned},
|
||||
{"right-margin", false, &conf_verify_unsigned},
|
||||
|
||||
{"border", false, &verify_bar_border},
|
||||
{"font", false, &conf_verify_font},
|
||||
{"font-shaping", false, &conf_verify_font_shaping},
|
||||
{"foreground", false, &conf_verify_color},
|
||||
|
||||
{"left", false, &verify_module_list},
|
||||
{"center", false, &verify_module_list},
|
||||
{"right", false, &verify_module_list},
|
||||
|
||||
{"trackpad-sensitivity", false, &conf_verify_int},
|
||||
{"trackpad-sensitivity", false, &conf_verify_unsigned},
|
||||
|
||||
{NULL, false, NULL},
|
||||
};
|
||||
|
|
|
@ -26,15 +26,14 @@ chain_pop(keychain_t *chain)
|
|||
tll_pop_back(*chain);
|
||||
}
|
||||
|
||||
const char *conf_err_prefix(
|
||||
const keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
const char *conf_err_prefix(const keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_string(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_int(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_unsigned(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_bool(keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_enum(keychain_t *chain, const struct yml_node *node,
|
||||
const char *values[], size_t count);
|
||||
bool conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count);
|
||||
bool conf_verify_list(keychain_t *chain, const struct yml_node *node,
|
||||
bool (*verify)(keychain_t *chain, const struct yml_node *node));
|
||||
bool conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
||||
|
@ -43,6 +42,7 @@ bool conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
|||
bool conf_verify_on_click(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_color(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_font(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_particle(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node);
|
||||
|
|
237
config.c
237
config.c
|
@ -1,9 +1,10 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
|
@ -20,9 +21,7 @@
|
|||
static uint8_t
|
||||
hex_nibble(char hex)
|
||||
{
|
||||
assert((hex >= '0' && hex <= '9') ||
|
||||
(hex >= 'a' && hex <= 'f') ||
|
||||
(hex >= 'A' && hex <= 'F'));
|
||||
assert((hex >= '0' && hex <= '9') || (hex >= 'a' && hex <= 'f') || (hex >= 'A' && hex <= 'F'));
|
||||
|
||||
if (hex >= '0' && hex <= '9')
|
||||
return hex - '0';
|
||||
|
@ -56,9 +55,9 @@ conf_to_color(const struct yml_node *node)
|
|||
alpha |= alpha << 8;
|
||||
|
||||
return (pixman_color_t){
|
||||
.red = (uint32_t)(red << 8 | red) * alpha / 0xffff,
|
||||
.red = (uint32_t)(red << 8 | red) * alpha / 0xffff,
|
||||
.green = (uint32_t)(green << 8 | green) * alpha / 0xffff,
|
||||
.blue = (uint32_t)(blue << 8 | blue) * alpha / 0xffff,
|
||||
.blue = (uint32_t)(blue << 8 | blue) * alpha / 0xffff,
|
||||
.alpha = alpha,
|
||||
};
|
||||
}
|
||||
|
@ -66,7 +65,69 @@ conf_to_color(const struct yml_node *node)
|
|||
struct fcft_font *
|
||||
conf_to_font(const struct yml_node *node)
|
||||
{
|
||||
return fcft_from_name(1, &(const char *){yml_value_as_string(node)}, NULL);
|
||||
const char *font_spec = yml_value_as_string(node);
|
||||
|
||||
size_t count = 0;
|
||||
size_t size = 0;
|
||||
const char **fonts = NULL;
|
||||
|
||||
char *copy = strdup(font_spec);
|
||||
for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) {
|
||||
/* Trim spaces, strictly speaking not necessary, but looks nice :) */
|
||||
while (isspace(font[0]))
|
||||
font++;
|
||||
|
||||
if (font[0] == '\0')
|
||||
continue;
|
||||
|
||||
if (count + 1 > size) {
|
||||
size += 4;
|
||||
fonts = realloc(fonts, size * sizeof(fonts[0]));
|
||||
}
|
||||
|
||||
assert(count + 1 <= size);
|
||||
fonts[count++] = font;
|
||||
}
|
||||
|
||||
struct fcft_font *ret = fcft_from_name(count, fonts, NULL);
|
||||
|
||||
free(fonts);
|
||||
free(copy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum font_shaping
|
||||
conf_to_font_shaping(const struct yml_node *node)
|
||||
{
|
||||
const char *v = yml_value_as_string(node);
|
||||
|
||||
if (strcmp(v, "none") == 0)
|
||||
return FONT_SHAPE_NONE;
|
||||
|
||||
else if (strcmp(v, "graphemes") == 0) {
|
||||
static bool have_warned = false;
|
||||
|
||||
if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_GRAPHEME_SHAPING)) {
|
||||
have_warned = true;
|
||||
LOG_WARN("cannot enable grapheme shaping; no support in fcft");
|
||||
}
|
||||
return FONT_SHAPE_GRAPHEMES;
|
||||
}
|
||||
|
||||
else if (strcmp(v, "full") == 0) {
|
||||
static bool have_warned = false;
|
||||
|
||||
if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING)) {
|
||||
have_warned = true;
|
||||
LOG_WARN("cannot enable full text shaping; no support in fcft");
|
||||
}
|
||||
return FONT_SHAPE_FULL;
|
||||
}
|
||||
|
||||
else {
|
||||
assert(false);
|
||||
return FONT_SHAPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
struct deco *
|
||||
|
@ -84,25 +145,20 @@ conf_to_deco(const struct yml_node *node)
|
|||
}
|
||||
|
||||
static struct particle *
|
||||
particle_simple_list_from_config(const struct yml_node *node,
|
||||
struct conf_inherit inherited)
|
||||
particle_simple_list_from_config(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
size_t count = yml_list_length(node);
|
||||
struct particle *parts[count];
|
||||
|
||||
size_t idx = 0;
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
parts[idx] = conf_to_particle(it.node, inherited);
|
||||
}
|
||||
|
||||
/* Lazy-loaded function pointer to particle_list_new() */
|
||||
static struct particle *(*particle_list_new)(
|
||||
struct particle *common,
|
||||
struct particle *particles[], size_t count,
|
||||
int left_spacing, int right_spacing) = NULL;
|
||||
static struct particle *(*particle_list_new)(struct particle *common, struct particle *particles[], size_t count,
|
||||
int left_spacing, int right_spacing)
|
||||
= NULL;
|
||||
|
||||
if (particle_list_new == NULL) {
|
||||
const struct plugin *plug = plugin_load("list", PLUGIN_PARTICLE);
|
||||
|
@ -111,8 +167,8 @@ particle_simple_list_from_config(const struct yml_node *node,
|
|||
assert(particle_list_new != NULL);
|
||||
}
|
||||
|
||||
struct particle *common = particle_common_new(
|
||||
0, 0, NULL, fcft_clone(inherited.font), inherited.foreground, NULL);
|
||||
struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping,
|
||||
inherited.foreground, NULL);
|
||||
|
||||
return particle_list_new(common, parts, count, 0, 2);
|
||||
}
|
||||
|
@ -131,28 +187,53 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *right_margin = yml_get_value(pair.value, "right-margin");
|
||||
const struct yml_node *on_click = yml_get_value(pair.value, "on-click");
|
||||
const struct yml_node *font_node = yml_get_value(pair.value, "font");
|
||||
const struct yml_node *font_shaping_node = yml_get_value(pair.value, "font-shaping");
|
||||
const struct yml_node *foreground_node = yml_get_value(pair.value, "foreground");
|
||||
const struct yml_node *deco_node = yml_get_value(pair.value, "deco");
|
||||
|
||||
int left = margin != NULL ? yml_value_as_int(margin) :
|
||||
left_margin != NULL ? yml_value_as_int(left_margin) : 0;
|
||||
int right = margin != NULL ? yml_value_as_int(margin) :
|
||||
right_margin != NULL ? yml_value_as_int(right_margin) : 0;
|
||||
int left = margin != NULL ? yml_value_as_int(margin) : left_margin != NULL ? yml_value_as_int(left_margin) : 0;
|
||||
int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0;
|
||||
|
||||
const char *on_click_templates[MOUSE_BTN_COUNT] = {NULL};
|
||||
char *on_click_templates[MOUSE_BTN_COUNT] = {NULL};
|
||||
if (on_click != NULL) {
|
||||
const char *legacy = yml_value_as_string(on_click);
|
||||
const char *yml_legacy = yml_value_as_string(on_click);
|
||||
|
||||
if (yml_legacy != NULL) {
|
||||
char *legacy = NULL;
|
||||
|
||||
if (yml_legacy[0] == '~' && yml_legacy[1] == '/') {
|
||||
const char *home_dir = getenv("HOME");
|
||||
|
||||
if (home_dir != NULL)
|
||||
if (asprintf(&legacy, "%s/%s", home_dir, yml_legacy + 2) < 0)
|
||||
legacy = NULL;
|
||||
|
||||
if (legacy == NULL)
|
||||
legacy = strdup(yml_legacy);
|
||||
} else
|
||||
legacy = strdup(yml_legacy);
|
||||
|
||||
if (legacy != NULL)
|
||||
on_click_templates[MOUSE_BTN_LEFT] = legacy;
|
||||
}
|
||||
|
||||
if (yml_is_dict(on_click)) {
|
||||
for (struct yml_dict_iter it = yml_dict_iter(on_click);
|
||||
it.key != NULL;
|
||||
yml_dict_next(&it))
|
||||
{
|
||||
else if (yml_is_dict(on_click)) {
|
||||
for (struct yml_dict_iter it = yml_dict_iter(on_click); it.key != NULL; yml_dict_next(&it)) {
|
||||
const char *key = yml_value_as_string(it.key);
|
||||
const char *template = yml_value_as_string(it.value);
|
||||
const char *yml_template = yml_value_as_string(it.value);
|
||||
|
||||
char *template = NULL;
|
||||
|
||||
if (yml_template[0] == '~' && yml_template[1] == '/') {
|
||||
const char *home_dir = getenv("HOME");
|
||||
|
||||
if (home_dir != NULL)
|
||||
if (asprintf(&template, "%s/%s", home_dir, yml_template + 2) < 0)
|
||||
template = NULL;
|
||||
|
||||
if (template == NULL)
|
||||
template = strdup(yml_template);
|
||||
} else
|
||||
template = strdup(yml_template);
|
||||
|
||||
if (strcmp(key, "left") == 0)
|
||||
on_click_templates[MOUSE_BTN_LEFT] = template;
|
||||
|
@ -164,6 +245,10 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
|
|||
on_click_templates[MOUSE_BTN_WHEEL_UP] = template;
|
||||
else if (strcmp(key, "wheel-down") == 0)
|
||||
on_click_templates[MOUSE_BTN_WHEEL_DOWN] = template;
|
||||
else if (strcmp(key, "previous") == 0)
|
||||
on_click_templates[MOUSE_BTN_PREVIOUS] = template;
|
||||
else if (strcmp(key, "next") == 0)
|
||||
on_click_templates[MOUSE_BTN_NEXT] = template;
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
|
@ -181,14 +266,14 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
|
|||
* clone the font, since each particle takes ownership of its own
|
||||
* font.
|
||||
*/
|
||||
struct fcft_font *font = font_node != NULL
|
||||
? conf_to_font(font_node) : fcft_clone(inherited.font);
|
||||
pixman_color_t foreground = foreground_node != NULL
|
||||
? conf_to_color(foreground_node) : inherited.foreground;
|
||||
struct fcft_font *font = font_node != NULL ? conf_to_font(font_node) : fcft_clone(inherited.font);
|
||||
enum font_shaping font_shaping
|
||||
= font_shaping_node != NULL ? conf_to_font_shaping(font_shaping_node) : inherited.font_shaping;
|
||||
pixman_color_t foreground = foreground_node != NULL ? conf_to_color(foreground_node) : inherited.foreground;
|
||||
|
||||
/* Instantiate base/common particle */
|
||||
struct particle *common = particle_common_new(
|
||||
left, right, on_click_templates, font, foreground, deco);
|
||||
struct particle *common
|
||||
= particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco);
|
||||
|
||||
const struct particle_iface *iface = plugin_load_particle(type);
|
||||
|
||||
|
@ -204,6 +289,8 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
|
||||
struct bar_config conf = {
|
||||
.backend = backend,
|
||||
.layer = BAR_LAYER_BOTTOM,
|
||||
.font_shaping = FONT_SHAPE_FULL,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -214,8 +301,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
conf.height = yml_value_as_int(height);
|
||||
|
||||
const struct yml_node *location = yml_get_value(bar, "location");
|
||||
conf.location = strcmp(yml_value_as_string(location), "top") == 0
|
||||
? BAR_TOP : BAR_BOTTOM;
|
||||
conf.location = strcmp(yml_value_as_string(location), "top") == 0 ? BAR_TOP : BAR_BOTTOM;
|
||||
|
||||
const struct yml_node *background = yml_get_value(bar, "background");
|
||||
conf.background = conf_to_color(background);
|
||||
|
@ -228,6 +314,21 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
if (monitor != NULL)
|
||||
conf.monitor = yml_value_as_string(monitor);
|
||||
|
||||
const struct yml_node *layer = yml_get_value(bar, "layer");
|
||||
if (layer != NULL) {
|
||||
const char *tmp = yml_value_as_string(layer);
|
||||
if (strcmp(tmp, "overlay") == 0)
|
||||
conf.layer = BAR_LAYER_OVERLAY;
|
||||
else if (strcmp(tmp, "top") == 0)
|
||||
conf.layer = BAR_LAYER_TOP;
|
||||
else if (strcmp(tmp, "bottom") == 0)
|
||||
conf.layer = BAR_LAYER_BOTTOM;
|
||||
else if (strcmp(tmp, "background") == 0)
|
||||
conf.layer = BAR_LAYER_BACKGROUND;
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
|
||||
const struct yml_node *spacing = yml_get_value(bar, "spacing");
|
||||
if (spacing != NULL)
|
||||
conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing);
|
||||
|
@ -252,15 +353,16 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
if (right_margin != NULL)
|
||||
conf.right_margin = yml_value_as_int(right_margin);
|
||||
|
||||
const struct yml_node *trackpad_sensitivity =
|
||||
yml_get_value(bar, "trackpad-sensitivity");
|
||||
conf.trackpad_sensitivity = trackpad_sensitivity != NULL
|
||||
? yml_value_as_int(trackpad_sensitivity)
|
||||
: 30;
|
||||
const struct yml_node *trackpad_sensitivity = yml_get_value(bar, "trackpad-sensitivity");
|
||||
conf.trackpad_sensitivity = trackpad_sensitivity != NULL ? yml_value_as_int(trackpad_sensitivity) : 30;
|
||||
|
||||
const struct yml_node *border = yml_get_value(bar, "border");
|
||||
if (border != NULL) {
|
||||
const struct yml_node *width = yml_get_value(border, "width");
|
||||
const struct yml_node *left_width = yml_get_value(border, "left-width");
|
||||
const struct yml_node *right_width = yml_get_value(border, "right-width");
|
||||
const struct yml_node *top_width = yml_get_value(border, "top-width");
|
||||
const struct yml_node *bottom_width = yml_get_value(border, "bottom-width");
|
||||
const struct yml_node *color = yml_get_value(border, "color");
|
||||
const struct yml_node *margin = yml_get_value(border, "margin");
|
||||
const struct yml_node *left_margin = yml_get_value(border, "left-margin");
|
||||
|
@ -269,16 +371,24 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
const struct yml_node *bottom_margin = yml_get_value(border, "bottom-margin");
|
||||
|
||||
if (width != NULL)
|
||||
conf.border.width = yml_value_as_int(width);
|
||||
conf.border.left_width = conf.border.right_width = conf.border.top_width = conf.border.bottom_width
|
||||
= yml_value_as_int(width);
|
||||
|
||||
if (left_width != NULL)
|
||||
conf.border.left_width = yml_value_as_int(left_width);
|
||||
if (right_width != NULL)
|
||||
conf.border.right_width = yml_value_as_int(right_width);
|
||||
if (top_width != NULL)
|
||||
conf.border.top_width = yml_value_as_int(top_width);
|
||||
if (bottom_width != NULL)
|
||||
conf.border.bottom_width = yml_value_as_int(bottom_width);
|
||||
|
||||
if (color != NULL)
|
||||
conf.border.color = conf_to_color(color);
|
||||
|
||||
if (margin != NULL)
|
||||
conf.border.left_margin =
|
||||
conf.border.right_margin =
|
||||
conf.border.top_margin =
|
||||
conf.border.bottom_margin = yml_value_as_int(margin);
|
||||
conf.border.left_margin = conf.border.right_margin = conf.border.top_margin = conf.border.bottom_margin
|
||||
= yml_value_as_int(margin);
|
||||
|
||||
if (left_margin != NULL)
|
||||
conf.border.left_margin = yml_value_as_int(left_margin);
|
||||
|
@ -298,6 +408,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
* foreground color at top-level.
|
||||
*/
|
||||
struct fcft_font *font = fcft_from_name(1, &(const char *){"sans"}, NULL);
|
||||
enum font_shaping font_shaping = FONT_SHAPE_FULL;
|
||||
pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */
|
||||
|
||||
const struct yml_node *font_node = yml_get_value(bar, "font");
|
||||
|
@ -306,12 +417,17 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
font = conf_to_font(font_node);
|
||||
}
|
||||
|
||||
const struct yml_node *font_shaping_node = yml_get_value(bar, "font-shaping");
|
||||
if (font_shaping_node != NULL)
|
||||
font_shaping = conf_to_font_shaping(font_shaping_node);
|
||||
|
||||
const struct yml_node *foreground_node = yml_get_value(bar, "foreground");
|
||||
if (foreground_node != NULL)
|
||||
foreground = conf_to_color(foreground_node);
|
||||
|
||||
struct conf_inherit inherited = {
|
||||
.font = font,
|
||||
.font_shaping = font_shaping,
|
||||
.foreground = foreground,
|
||||
};
|
||||
|
||||
|
@ -327,10 +443,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
struct module **mods = calloc(count, sizeof(*mods));
|
||||
|
||||
size_t idx = 0;
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
struct yml_dict_iter m = yml_dict_iter(it.node);
|
||||
const char *mod_name = yml_value_as_string(m.key);
|
||||
|
||||
|
@ -341,14 +454,14 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
* applied to all its particles.
|
||||
*/
|
||||
const struct yml_node *mod_font = yml_get_value(m.value, "font");
|
||||
const struct yml_node *mod_foreground = yml_get_value(
|
||||
m.value, "foreground");
|
||||
const struct yml_node *mod_font_shaping = yml_get_value(m.value, "font-shaping");
|
||||
const struct yml_node *mod_foreground = yml_get_value(m.value, "foreground");
|
||||
|
||||
struct conf_inherit mod_inherit = {
|
||||
.font = mod_font != NULL
|
||||
? conf_to_font(mod_font) : inherited.font,
|
||||
.foreground = mod_foreground != NULL
|
||||
? conf_to_color(mod_foreground) : inherited.foreground,
|
||||
.font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font,
|
||||
.font_shaping
|
||||
= mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping,
|
||||
.foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground,
|
||||
};
|
||||
|
||||
const struct module_iface *iface = plugin_load_module(mod_name);
|
||||
|
|
10
config.h
10
config.h
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <fcft/fcft.h>
|
||||
#include "yml.h"
|
||||
#include "bar/bar.h"
|
||||
#include "font-shaping.h"
|
||||
#include "yml.h"
|
||||
#include <fcft/fcft.h>
|
||||
|
||||
struct bar;
|
||||
struct particle;
|
||||
|
@ -16,12 +17,13 @@ struct bar *conf_to_bar(const struct yml_node *bar, enum bar_backend backend);
|
|||
|
||||
pixman_color_t conf_to_color(const struct yml_node *node);
|
||||
struct fcft_font *conf_to_font(const struct yml_node *node);
|
||||
enum font_shaping conf_to_font_shaping(const struct yml_node *node);
|
||||
|
||||
struct conf_inherit {
|
||||
const struct fcft_font *font;
|
||||
enum font_shaping font_shaping;
|
||||
pixman_color_t foreground;
|
||||
};
|
||||
|
||||
struct particle *conf_to_particle(
|
||||
const struct yml_node *node, struct conf_inherit inherited);
|
||||
struct particle *conf_to_particle(const struct yml_node *node, struct conf_inherit inherited);
|
||||
struct deco *conf_to_deco(const struct yml_node *node);
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
struct deco {
|
||||
void *private;
|
||||
void (*expose)(const struct deco *deco, pixman_image_t *pix,
|
||||
int x, int y, int width, int height);
|
||||
void (*expose)(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height);
|
||||
void (*destroy)(struct deco *deco);
|
||||
};
|
||||
|
||||
#define DECORATION_COMMON_ATTRS \
|
||||
{NULL, false, NULL}
|
||||
#define DECORATION_COMMON_ATTRS \
|
||||
{ \
|
||||
NULL, false, NULL \
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
//struct rgba color;
|
||||
struct private
|
||||
{
|
||||
// struct rgba color;
|
||||
pixman_color_t color;
|
||||
};
|
||||
|
||||
|
@ -22,9 +23,7 @@ static void
|
|||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &d->color, 1,
|
||||
&(pixman_rectangle16_t){x, y, width, height});
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, height});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
|
|
91
decorations/border.c
Normal file
91
decorations/border.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#define LOG_MODULE "border"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
struct private
|
||||
{
|
||||
pixman_color_t color;
|
||||
int size;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct deco *deco)
|
||||
{
|
||||
struct private *d = deco->private;
|
||||
free(d);
|
||||
free(deco);
|
||||
}
|
||||
|
||||
static void
|
||||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
/* Top */
|
||||
{x, y, width, min(d->size, height)},
|
||||
|
||||
/* Bottom */
|
||||
{x, max(y + height - d->size, y), width, min(d->size, height)},
|
||||
|
||||
/* Left */
|
||||
{x, y, min(d->size, width), height},
|
||||
|
||||
/* Right */
|
||||
{max(x + width - d->size, x), y, min(d->size, width), height},
|
||||
});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
border_new(pixman_color_t color, int size)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->color = color;
|
||||
priv->size = size;
|
||||
|
||||
struct deco *deco = calloc(1, sizeof(*deco));
|
||||
deco->private = priv;
|
||||
deco->expose = &expose;
|
||||
deco->destroy = &destroy;
|
||||
|
||||
return deco;
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
from_conf(const struct yml_node *node)
|
||||
{
|
||||
const struct yml_node *color = yml_get_value(node, "color");
|
||||
const struct yml_node *size = yml_get_value(node, "size");
|
||||
return border_new(conf_to_color(color), size != NULL ? yml_value_as_int(size) : 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"color", true, &conf_verify_color},
|
||||
{"size", false, &conf_verify_unsigned},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct deco_iface deco_border_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct deco_iface iface __attribute__((weak, alias("deco_border_iface")));
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
|
||||
|
||||
decorations = []
|
||||
foreach deco : ['background', 'stack', 'underline']
|
||||
foreach deco : ['background', 'border', 'stack', 'underline', 'overline']
|
||||
if plugs_as_libs
|
||||
shared_module('@0@'.format(deco), '@0@.c'.format(deco),
|
||||
dependencies: deco_sdk,
|
||||
|
|
71
decorations/overline.c
Normal file
71
decorations/overline.c
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private
|
||||
{
|
||||
int size;
|
||||
pixman_color_t color;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct deco *deco)
|
||||
{
|
||||
struct private *d = deco->private;
|
||||
free(d);
|
||||
free(deco);
|
||||
}
|
||||
|
||||
static void
|
||||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, d->size});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
overline_new(int size, pixman_color_t color)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->size = size;
|
||||
priv->color = color;
|
||||
|
||||
struct deco *deco = calloc(1, sizeof(*deco));
|
||||
deco->private = priv;
|
||||
deco->expose = &expose;
|
||||
deco->destroy = &destroy;
|
||||
|
||||
return deco;
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
from_conf(const struct yml_node *node)
|
||||
{
|
||||
const struct yml_node *size = yml_get_value(node, "size");
|
||||
const struct yml_node *color = yml_get_value(node, "color");
|
||||
return overline_new(yml_value_as_int(size), conf_to_color(color));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"size", true, &conf_verify_unsigned},
|
||||
{"color", true, &conf_verify_color},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct deco_iface deco_overline_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct deco_iface iface __attribute__((weak, alias("deco_overline_iface")));
|
||||
#endif
|
|
@ -1,13 +1,14 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#define LOG_MODULE "stack"
|
||||
#include "../log.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
struct deco **decos;
|
||||
size_t count;
|
||||
};
|
||||
|
@ -57,10 +58,7 @@ from_conf(const struct yml_node *node)
|
|||
struct deco *decos[count];
|
||||
size_t idx = 0;
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
decos[idx] = conf_to_deco(it.node);
|
||||
}
|
||||
|
||||
|
@ -75,10 +73,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_list_iter it = yml_list_iter(node);
|
||||
it.node != NULL;
|
||||
yml_list_next(&it))
|
||||
{
|
||||
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
|
||||
if (!conf_verify_decoration(chain, it.node))
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
int size;
|
||||
pixman_color_t color;
|
||||
};
|
||||
|
@ -22,9 +23,8 @@ static void
|
|||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &d->color, 1,
|
||||
&(pixman_rectangle16_t){x, y + height - d->size, width, d->size});
|
||||
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1,
|
||||
&(pixman_rectangle16_t){x, y + height - d->size, width, d->size});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
|
@ -54,7 +54,7 @@ static bool
|
|||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"size", true, &conf_verify_int},
|
||||
{"size", true, &conf_verify_unsigned},
|
||||
{"color", true, &conf_verify_color},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
|
|
@ -1,18 +1,86 @@
|
|||
sh = find_program('sh', native: true)
|
||||
|
||||
scdoc = dependency('scdoc', native: true)
|
||||
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
|
||||
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
|
||||
|
||||
foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
|
||||
'yambar-modules-alsa.5.scd', 'yambar-modules-backlight.5.scd',
|
||||
'yambar-modules-battery.5.scd', 'yambar-modules-clock.5.scd',
|
||||
'yambar-modules-i3.5.scd', 'yambar-modules-label.5.scd',
|
||||
'yambar-modules-mpd.5.scd', 'yambar-modules-network.5.scd',
|
||||
'yambar-modules-removables.5.scd', 'yambar-modules-river.5.scd',
|
||||
'yambar-modules-script.5.scd', 'yambar-modules-sway-xkb.5.scd',
|
||||
'yambar-modules-sway.5.scd', 'yambar-modules-xkb.5.scd',
|
||||
'yambar-modules-xwindow.5.scd', 'yambar-modules.5.scd',
|
||||
'yambar-particles.5.scd', 'yambar-tags.5.scd']
|
||||
plugin_pages = []
|
||||
if plugin_alsa_enabled
|
||||
plugin_pages += ['yambar-modules-alsa.5.scd']
|
||||
endif
|
||||
if plugin_backlight_enabled
|
||||
plugin_pages += ['yambar-modules-backlight.5.scd']
|
||||
endif
|
||||
if plugin_battery_enabled
|
||||
plugin_pages += ['yambar-modules-battery.5.scd']
|
||||
endif
|
||||
if plugin_clock_enabled
|
||||
plugin_pages += ['yambar-modules-clock.5.scd']
|
||||
endif
|
||||
if plugin_cpu_enabled
|
||||
plugin_pages += ['yambar-modules-cpu.5.scd']
|
||||
endif
|
||||
if plugin_disk_io_enabled
|
||||
plugin_pages += ['yambar-modules-disk-io.5.scd']
|
||||
endif
|
||||
if plugin_dwl_enabled
|
||||
plugin_pages += ['yambar-modules-dwl.5.scd']
|
||||
endif
|
||||
if plugin_foreign_toplevel_enabled
|
||||
plugin_pages += ['yambar-modules-foreign-toplevel.5.scd']
|
||||
endif
|
||||
if plugin_mem_enabled
|
||||
plugin_pages += ['yambar-modules-mem.5.scd']
|
||||
endif
|
||||
if plugin_mpd_enabled
|
||||
plugin_pages += ['yambar-modules-mpd.5.scd']
|
||||
endif
|
||||
if plugin_mpris_enabled
|
||||
plugin_pages += ['yambar-modules-mpris.5.scd']
|
||||
endif
|
||||
if plugin_i3_enabled
|
||||
plugin_pages += ['yambar-modules-i3.5.scd']
|
||||
plugin_pages += ['yambar-modules-sway.5.scd']
|
||||
endif
|
||||
if plugin_label_enabled
|
||||
plugin_pages += ['yambar-modules-label.5.scd']
|
||||
endif
|
||||
if plugin_network_enabled
|
||||
plugin_pages += ['yambar-modules-network.5.scd']
|
||||
endif
|
||||
if plugin_niri_language_enabled
|
||||
plugin_pages += ['yambar-modules-niri-language.5.scd']
|
||||
endif
|
||||
if plugin_niri_workspaces_enabled
|
||||
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
|
||||
endif
|
||||
if plugin_pipewire_enabled
|
||||
plugin_pages += ['yambar-modules-pipewire.5.scd']
|
||||
endif
|
||||
if plugin_pulse_enabled
|
||||
plugin_pages += ['yambar-modules-pulse.5.scd']
|
||||
endif
|
||||
if plugin_removables_enabled
|
||||
plugin_pages += ['yambar-modules-removables.5.scd']
|
||||
endif
|
||||
if plugin_river_enabled
|
||||
plugin_pages += ['yambar-modules-river.5.scd']
|
||||
endif
|
||||
if plugin_script_enabled
|
||||
plugin_pages += ['yambar-modules-script.5.scd']
|
||||
endif
|
||||
if plugin_sway_xkb_enabled
|
||||
plugin_pages += ['yambar-modules-sway-xkb.5.scd']
|
||||
endif
|
||||
if plugin_xkb_enabled
|
||||
plugin_pages += ['yambar-modules-xkb.5.scd']
|
||||
endif
|
||||
|
||||
foreach man_src : ['yambar.1.scd',
|
||||
'yambar.5.scd',
|
||||
'yambar-decorations.5.scd',
|
||||
'yambar-modules.5.scd',
|
||||
'yambar-particles.5.scd',
|
||||
'yambar-tags.5.scd'] + plugin_pages
|
||||
parts = man_src.split('.')
|
||||
name = parts[-3]
|
||||
section = parts[-2]
|
||||
|
@ -22,7 +90,7 @@ foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
|
|||
out,
|
||||
output: out,
|
||||
input: man_src,
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
|
||||
capture: true,
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
|
||||
|
|
|
@ -23,7 +23,7 @@ This decoration sets the particles background color.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
|
@ -49,7 +49,7 @@ bottom of the particle.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| size
|
||||
: int
|
||||
: yes
|
||||
|
@ -70,9 +70,74 @@ content:
|
|||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# OVERLINE
|
||||
|
||||
Similar to _underline_, this decoration renders a line of configurable
|
||||
size and color at the top of the particle.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| size
|
||||
: int
|
||||
: yes
|
||||
: The size (height/thickness) of the line, in pixels
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
: The color of the line. See *yambar*(5) for format.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
string:
|
||||
deco:
|
||||
overline:
|
||||
size: 2
|
||||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# BORDER
|
||||
|
||||
This decoration renders a border of configurable size (i.e border
|
||||
width) around the particle.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
: The color of the line. See *yambar*(5) for format.
|
||||
| size
|
||||
: int
|
||||
: no
|
||||
: Border width, in pixels. Defaults to 1px.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
string:
|
||||
deco:
|
||||
border:
|
||||
size: 2
|
||||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# STACK
|
||||
|
||||
This particles combines multiple decorations.
|
||||
This particle combines multiple decorations.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
|
|
|
@ -7,13 +7,21 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| online
|
||||
: bool
|
||||
: True when the ALSA device has successfully been opened
|
||||
| dB
|
||||
: range
|
||||
: Volume level (in dB), with min and max as start and end range
|
||||
values.
|
||||
| volume
|
||||
: range
|
||||
: Volume level, with min and max as start and end range values
|
||||
: Volume level (raw), with min and max as start and end range values
|
||||
| percent
|
||||
: range
|
||||
: Volume level, as a percentage
|
||||
: Volume level, as a percentage. This value is based on the *dB* tag
|
||||
if available, otherwise the *volume* tag.
|
||||
| muted
|
||||
: bool
|
||||
: True if muted, otherwise false
|
||||
|
@ -24,7 +32,7 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| card
|
||||
: string
|
||||
: yes
|
||||
|
@ -33,6 +41,17 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
|
|||
: string
|
||||
: yes
|
||||
: Mixer channel to monitor. _Master_ might work.
|
||||
| volume
|
||||
: string
|
||||
: no
|
||||
: The name of the channel to use as source for the volume level
|
||||
(default: first available channel, usually "Front Left").
|
||||
| muted
|
||||
: string
|
||||
: no
|
||||
: The name of the channel to use as source for the muted state
|
||||
(default: first available channel, usually "Front Left").
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ _/sys/class/backlight_, and uses *udev* to monitor for changes.
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| brightness
|
||||
: range
|
||||
: The current brightness level, in absolute value
|
||||
|
|
|
@ -8,11 +8,22 @@ battery - This module reads battery status
|
|||
This module reads battery status from _/sys/class/power_supply_ and
|
||||
uses *udev* to monitor for changes.
|
||||
|
||||
Note that it is common (and "normal") for batteries to be in the state
|
||||
*unknown* under certain conditions.
|
||||
|
||||
For example, some have been seen to enter the *unknown* state when
|
||||
charging and the capacity reaches ~90%. The battery then stays in
|
||||
*unknown*, rather than *charging*, until it has been fully charged and
|
||||
enters the state *full*.
|
||||
|
||||
This does not happen with all batteries, and other batteries may enter
|
||||
the state *unknown* under other conditions.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: Battery device name
|
||||
|
@ -38,7 +49,7 @@ uses *udev* to monitor for changes.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: yes
|
||||
|
@ -46,7 +57,20 @@ uses *udev* to monitor for changes.
|
|||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: How often, in seconds, to poll for capacity changes (default=*60*). Set to `0` to disable polling (*warning*: many batteries do not support asynchronous reporting).
|
||||
: How often, in milliseconds, to poll for capacity changes
|
||||
(default=*60000*). Set to `0` to disable polling (*warning*: many
|
||||
batteries do not support asynchronous reporting). Cannot be less
|
||||
than 250ms.
|
||||
| battery-scale
|
||||
: int
|
||||
: no
|
||||
: How much to scale down the battery charge amount. Some batteries
|
||||
report too high resulting in bad discharge estimates. Default=1.
|
||||
| smoothing-secs
|
||||
: int
|
||||
: no
|
||||
: How many seconds to perform smoothing over for battery discharge
|
||||
estimates. Default=100s.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
@ -55,7 +79,7 @@ bar:
|
|||
left:
|
||||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30
|
||||
poll-interval: 30000
|
||||
content:
|
||||
string: {text: "BAT: {capacity}% {estimate}"}
|
||||
```
|
||||
|
|
|
@ -7,7 +7,7 @@ clock - This module provides the current date and time
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| time
|
||||
: string
|
||||
: Current time, formatted using the _time-format_ attribute
|
||||
|
@ -20,7 +20,7 @@ clock - This module provides the current date and time
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| time-format
|
||||
: string
|
||||
: no
|
||||
|
@ -29,6 +29,10 @@ clock - This module provides the current date and time
|
|||
: string
|
||||
: no
|
||||
: *strftime* formatter for the _date_ date (default=*%x*)
|
||||
| utc
|
||||
: bool
|
||||
: no
|
||||
: Use GMT instead of local timezone (default=false)
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
|
79
doc/yambar-modules-cpu.5.scd
Normal file
79
doc/yambar-modules-cpu.5.scd
Normal file
|
@ -0,0 +1,79 @@
|
|||
yambar-modules-cpu(5)
|
||||
|
||||
# NAME
|
||||
cpu - This module provides the CPU usage
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module reports CPU usage, in percent. The _content_ particle is a
|
||||
template that is instantiated once for each core, and once for the
|
||||
total CPU usage.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: Core ID. 0..n represents individual cores, and -1 represents the
|
||||
total usage
|
||||
| cpu
|
||||
: range
|
||||
: Current usage of CPU core {id}, in percent
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: Refresh interval of the CPU usage stats in milliseconds
|
||||
(default=500). Cannot be less then 250ms.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Display total CPU usage as a number
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- cpu:
|
||||
poll-interval: 2500
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
id < 0:
|
||||
- string: {text: , font: Font Awesome 6 Free:style=solid}
|
||||
- string: {text: "{cpu}%"}
|
||||
```
|
||||
|
||||
## Display a vertical bar for each core
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- cpu:
|
||||
poll-interval: 2500
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
id >= 0:
|
||||
- ramp:
|
||||
tag: cpu
|
||||
items:
|
||||
- string: {text: ▁}
|
||||
- string: {text: ▂}
|
||||
- string: {text: ▃}
|
||||
- string: {text: ▄}
|
||||
- string: {text: ▅}
|
||||
- string: {text: ▆}
|
||||
- string: {text: ▇}
|
||||
- string: {text: █}
|
||||
```
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
85
doc/yambar-modules-disk-io.5.scd
Normal file
85
doc/yambar-modules-disk-io.5.scd
Normal file
|
@ -0,0 +1,85 @@
|
|||
yambar-modules-disk-io(5)
|
||||
|
||||
# NAME
|
||||
disk-io - This module keeps track of the amount of bytes being
|
||||
read/written from/to disk. It can distinguish between all partitions
|
||||
currently present in the machine.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| device
|
||||
: string
|
||||
: Name of the device being tracked (use the command *lsblk* to see these).
|
||||
There is a special device, "Total", that reports the total stats
|
||||
for the machine
|
||||
| is_disk
|
||||
: boolean
|
||||
: whether or not the device is a disk (e.g., sda, sdb) or a partition
|
||||
(e.g., sda1, sda2, ...). "Total" is advertised as a disk.
|
||||
| read_speed
|
||||
: int
|
||||
: bytes read, in bytes/s
|
||||
| write_speed
|
||||
: int
|
||||
: bytes written, in bytes/s
|
||||
| ios_in_progress
|
||||
: int
|
||||
: number of ios that are happening at the time of polling
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: Refresh interval of disk's stats in milliseconds (default=500).
|
||||
Cannot be less then 250ms.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
This reports the total amount of bytes being read and written every second,
|
||||
formatting in b/s, kb/s, mb/s, or gb/s, as appropriate.
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- disk-io:
|
||||
poll-interval: 1000
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
device == Total:
|
||||
list:
|
||||
items:
|
||||
- string: {text: "Total read: "}
|
||||
- map:
|
||||
default: {string: {text: "{read_speed} B/s"}}
|
||||
conditions:
|
||||
read_speed > 1073741824:
|
||||
string: {text: "{read_speed:gib} GB/s"}
|
||||
read_speed > 1048576:
|
||||
string: {text: "{read_speed:mib} MB/s"}
|
||||
read_speed > 1024:
|
||||
string: {text: "{read_speed:kib} KB/s"}
|
||||
- string: {text: " | "}
|
||||
- string: {text: "Total written: "}
|
||||
- map:
|
||||
default: {string: {text: "{write_speed} B/s"}}
|
||||
conditions:
|
||||
write_speed > 1073741824:
|
||||
string: {text: "{write_speed:gib} GB/s"}
|
||||
write_speed > 1048576:
|
||||
string: {text: "{write_speed:mib} MB/s"}
|
||||
write_speed > 1024:
|
||||
string: {text: "{write_speed:kib} KB/s"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
107
doc/yambar-modules-dwl.5.scd
Normal file
107
doc/yambar-modules-dwl.5.scd
Normal file
|
@ -0,0 +1,107 @@
|
|||
yambar-modules-dwl(5)
|
||||
|
||||
# NAME
|
||||
dwl - This module provides information about dwl tags, and information.
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module provides a map of each tags present in dwl.
|
||||
|
||||
Each tags has its _id_, its _name_, its status (_selected_, _empty_, _urgent_)
|
||||
and the global data like _title_, _appid_, _fullscreen_, _floating_,
|
||||
_selmon_, and _layout_). The tags start a 1. For needs where
|
||||
you only want information about the global data and not the _tags_,
|
||||
there is a tag with the id _0_ that contains only the global data.
|
||||
|
||||
This module will track *only* the monitor where yambar was launched on.
|
||||
If you have a multi monitor setup, please launch yambar on each of your
|
||||
monitors.
|
||||
|
||||
Please, be aware that only *one instance* of this module is supported.
|
||||
Running multiple instances at the same time may result in
|
||||
*undefined behavior*.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: dwl tag id.
|
||||
| name
|
||||
: string
|
||||
: The name of the tag (defaults to _id_ if not set).
|
||||
| selected
|
||||
: bool
|
||||
: True if the tag is currently selected.
|
||||
| empty
|
||||
: bool
|
||||
: True if there are no windows in the tag.
|
||||
| urgent
|
||||
: bool
|
||||
: True if the tag has the urgent flag set.
|
||||
| title
|
||||
: string
|
||||
: The currently focused window's title.
|
||||
| appid
|
||||
: string
|
||||
: The currently focused window's application id.
|
||||
| fullscreen
|
||||
: bool
|
||||
: True if there is a fullscreen window in the current tag.
|
||||
| floating
|
||||
: bool
|
||||
: True if there is a floating window in the current tag.
|
||||
| selmon
|
||||
: bool
|
||||
: True if the monitor is actually focused.
|
||||
| layout
|
||||
: string
|
||||
: The actual layout name of the tag.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| number-of-tags
|
||||
: int
|
||||
: yes
|
||||
: The number of defined tags in the dwl `config.def.h`.
|
||||
| name-of-tags
|
||||
: list
|
||||
: false
|
||||
: The name of the tags (must have the same length that _number-of-tags_).
|
||||
| dwl-info-filename
|
||||
: string
|
||||
: yes
|
||||
: The filepath to the log emitted by dwl when running.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- dwl:
|
||||
number-of-tags: 9
|
||||
dwl-info-filename: "/home/ogromny/dwl_info"
|
||||
name-of-tags: [ , , , , , , , , ]
|
||||
content:
|
||||
list:
|
||||
items:
|
||||
- map:
|
||||
conditions:
|
||||
# default tag
|
||||
id == 0: {string: {text: "{layout} {title}"}}
|
||||
|
||||
selected: {string: {text: "-> {name}"}}
|
||||
~empty: {string: {text: "{name}"}}
|
||||
urgent: {string: {text: "=> {name} <="}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
78
doc/yambar-modules-foreign-toplevel.5.scd
Normal file
78
doc/yambar-modules-foreign-toplevel.5.scd
Normal file
|
@ -0,0 +1,78 @@
|
|||
yambar-modules-foreign-toplevel(5)
|
||||
|
||||
# NAME
|
||||
foreign-toplevel - This module provides information about toplevel windows in Wayland
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module uses the _wlr foreign toplevel management_ Wayland
|
||||
protocol to provide information about currently open windows, such as
|
||||
their application ID, window title, and their current state
|
||||
(maximized/minimized/fullscreen/activated).
|
||||
|
||||
The configuration for the foreign-toplevel module specifies a
|
||||
_template_ particle. This particle will be instantiated once for each
|
||||
window.
|
||||
|
||||
Note: Wayland only.
|
||||
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| app-id
|
||||
: string
|
||||
: The application ID (typically the application name)
|
||||
| title
|
||||
: string
|
||||
: The window title
|
||||
| maximized
|
||||
: bool
|
||||
: True if the window is currently maximized
|
||||
| minimized
|
||||
: bool
|
||||
: True if the window is currently minimized
|
||||
| fullscreen
|
||||
: bool
|
||||
: True if the window is currently fullscreened
|
||||
| activated
|
||||
: bool
|
||||
: True if the window is currently activated (i.e. has focus)
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: Template particle that will be instantiated once for each window
|
||||
| all-monitors
|
||||
: bool
|
||||
: no
|
||||
: When set to true, only windows on the same monitor the bar will be
|
||||
used. The default is false.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: "{app-id}: {title}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
|
@ -22,10 +22,13 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: The workspace name
|
||||
| output
|
||||
: string
|
||||
: The output (monitor) the workspace is on
|
||||
| visible
|
||||
: bool
|
||||
: True if the workspace is currently visible (on any output)
|
||||
|
@ -35,6 +38,9 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
| urgent
|
||||
: bool
|
||||
: True if the workspace has the urgent flag set
|
||||
| empty
|
||||
: bool
|
||||
: True if the workspace is empty (Sway only)
|
||||
| state
|
||||
: string
|
||||
: One of *urgent*, *focused*, *unfocused* or *invisible* (note:
|
||||
|
@ -54,7 +60,7 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| content
|
||||
: associative array
|
||||
: yes
|
||||
|
@ -64,7 +70,15 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
| sort
|
||||
: enum
|
||||
: no
|
||||
: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
|
||||
: How to sort the list of workspaces; one of _none_, _native_, _ascending_ or _descending_, defaults to _none_. Use _native_ to sort numbered workspaces only.
|
||||
| strip-workspace-numbers
|
||||
: bool
|
||||
: no
|
||||
: If true, *N:* prefixes will be stripped from workspace names. Useful together with *sort*, to have the workspace order fixed.
|
||||
| persistent
|
||||
: list of strings
|
||||
: no
|
||||
: Persistent workspaces. I.e. workspaces that are never removed, even if empty.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
|
@ -91,10 +105,9 @@ bar:
|
|||
content:
|
||||
"":
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: "{name}"}}
|
||||
values:
|
||||
focused: {string: {text: "{name}*"}}
|
||||
conditions:
|
||||
state == focused: {string: {text: "{name}*"}}
|
||||
current: { string: {text: "{application}: {title}"}}
|
||||
```
|
||||
|
||||
|
|
52
doc/yambar-modules-mem.5.scd
Normal file
52
doc/yambar-modules-mem.5.scd
Normal file
|
@ -0,0 +1,52 @@
|
|||
yambar-modules-mem(5)
|
||||
|
||||
# NAME
|
||||
mem - This module provides the memory usage
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| free
|
||||
: int
|
||||
: Free memory in bytes
|
||||
| used
|
||||
: int
|
||||
: Used memory in bytes
|
||||
| total
|
||||
: int
|
||||
: Total memory in bytes
|
||||
| percent_free
|
||||
: range
|
||||
: Free memory in percent
|
||||
| percent_used
|
||||
: range
|
||||
: Used memory in percent
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| poll-interval
|
||||
: string
|
||||
: no
|
||||
: Refresh interval of the memory usage stats in milliseconds
|
||||
(default=500). Cannot be less then 250ms.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- mem:
|
||||
poll-interval: 2500
|
||||
content:
|
||||
string: {text: "{used:mb}MB"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
|
@ -7,7 +7,7 @@ mpd - This module provides MPD status such as currently playing artist/album/son
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| state
|
||||
: string
|
||||
: One of *offline*, *stopped*, *paused* or *playing*
|
||||
|
@ -20,6 +20,9 @@ mpd - This module provides MPD status such as currently playing artist/album/son
|
|||
| consume
|
||||
: bool
|
||||
: True if the *consume* flag is set
|
||||
| single
|
||||
: bool
|
||||
: True if the *single* flag is set
|
||||
| volume
|
||||
: range
|
||||
: Volume of MPD in percentage
|
||||
|
@ -32,6 +35,9 @@ mpd - This module provides MPD status such as currently playing artist/album/son
|
|||
| title
|
||||
: string
|
||||
: Title of currently playing song (also valid in *paused* state)
|
||||
| file
|
||||
: string
|
||||
: Filename or URL of currently playing song (also valid in *paused* state)
|
||||
| pos
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's current position
|
||||
|
@ -53,7 +59,7 @@ mpd - This module provides MPD status such as currently playing artist/album/son
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| host
|
||||
: string
|
||||
: yes
|
||||
|
|
101
doc/yambar-modules-mpris.5.scd
Normal file
101
doc/yambar-modules-mpris.5.scd
Normal file
|
@ -0,0 +1,101 @@
|
|||
yambar-modules-mpris(5)
|
||||
|
||||
# NAME
|
||||
mpris - This module provides MPRIS status such as currently playing artist/album/song
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| state
|
||||
: string
|
||||
: One of *offline*, *stopped*, *paused* or *playing*
|
||||
| shuffle
|
||||
: bool
|
||||
: True if the *shuffle* flag is set
|
||||
| repeat
|
||||
: string
|
||||
: One of *none*, *track* or *paylist*
|
||||
| volume
|
||||
: range
|
||||
: Volume in percentage
|
||||
| album
|
||||
: string
|
||||
: Currently playing album
|
||||
| artist
|
||||
: string
|
||||
: Artist of currently playing song
|
||||
| title
|
||||
: string
|
||||
: Title of currently playing song
|
||||
| file
|
||||
: string
|
||||
: Filename or URL of currently playing song
|
||||
| pos
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's current position
|
||||
(also see _elapsed_)
|
||||
| end
|
||||
: string
|
||||
: *%M:%S*-formatted string describing the song's total length (also
|
||||
see _duration_)
|
||||
| elapsed
|
||||
: realtime
|
||||
: Position in currently playing song, in milliseconds. Can be used
|
||||
with a _progress-bar_ particle.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| identities
|
||||
: list of string
|
||||
: yes
|
||||
: A list of MPRIS client identities
|
||||
| query_timeout
|
||||
: int
|
||||
: no
|
||||
: Dbus/MPRIS client connection timeout in ms. Try setting/incrementing
|
||||
this value if the module reports a timeout error. Defaults to 500.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
center:
|
||||
- mpris:
|
||||
identities:
|
||||
- "spotify"
|
||||
- "firefox"
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
state != offline && state != stopped:
|
||||
- string: {text: "{artist}", max: 30 }
|
||||
- string: {text: "-" }
|
||||
- string: {text: "{title}", max: 30 }
|
||||
```
|
||||
|
||||
# NOTE
|
||||
|
||||
The 'identity' refers a part of your clients DBus bus name.
|
||||
You can obtain a list of active client names using:
|
||||
|
||||
```
|
||||
Systemd: > busctl --user --list
|
||||
Playerctl: > playerctl --list-all
|
||||
Libdbus: > dbus-send --session --print-reply --type=method_call \
|
||||
--dest='org.freedesktop.DBus' /org org.freedesktop.DBus.ListNames
|
||||
```
|
||||
|
||||
MPRIS client bus names start with 'org.mpris.MediaPlayer2.<identity>'.
|
||||
For example, firefox may use the bus name:
|
||||
'org.mpris.MediaPlayer2.firefox.instance_1_7' which
|
||||
gives us the identity 'firefox'
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
|
@ -6,20 +6,31 @@ network - This module monitors network connection state
|
|||
# DESCRIPTION
|
||||
|
||||
This module monitors network connection state; disconnected/connected
|
||||
state and MAC/IP addresses.
|
||||
state and MAC/IP addresses. It instantiates the provided _content_
|
||||
particle for each network interface.
|
||||
|
||||
Note: while the module internally tracks all assigned IPv4/IPv6
|
||||
addresses, it currently exposes only a single IPv4 and a single IPv6
|
||||
address.
|
||||
address per network interface.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: Network interface name
|
||||
| type
|
||||
: string
|
||||
: Interface type (*ether*, *wlan*, *loopback*, or *ARPHRD_NNN*, where
|
||||
*N* is a number).
|
||||
| kind
|
||||
: string
|
||||
: Interface kind. Empty for non-virtual interfaces. For virtual
|
||||
interfaces, this value is taken from the _IFLA\_INFO\_KIND_ netlink
|
||||
attribute. Examples of valid values are *bond*, *bridge*, *gre*, *tun*
|
||||
and *veth*.
|
||||
| index
|
||||
: int
|
||||
: Network interface index
|
||||
|
@ -39,26 +50,74 @@ address.
|
|||
| ipv6
|
||||
: string
|
||||
: IPv6 address assigned to the interface, or *""* if none
|
||||
| ssid
|
||||
: string
|
||||
: SSID the adapter is connected to (Wi-Fi only)
|
||||
| signal
|
||||
: int
|
||||
: Signal strength, in dBm (Wi-Fi only)
|
||||
| quality
|
||||
: range
|
||||
: Quality of the signal, in percent (Wi-Fi only)
|
||||
| rx-bitrate
|
||||
: int
|
||||
: RX bitrate, in bits/s
|
||||
| tx-bitrate
|
||||
: int
|
||||
: TX bitrate in bits/s
|
||||
| dl-speed
|
||||
: int
|
||||
: Download speed in bits/s
|
||||
| ul-speed
|
||||
: int
|
||||
: Upload speed in bits/s
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| name
|
||||
: string
|
||||
: Name of network interface to monitor
|
||||
:< *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered volume
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered volume
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
| poll-interval
|
||||
: int
|
||||
: no
|
||||
: Periodically (in milliseconds) update the signal, quality, rx+tx bitrate, and
|
||||
ul+dl speed tags (default=0). Setting it to 0 disables updates. Cannot be less
|
||||
than 250ms.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
Display all Ethernet (including WLAN) devices. This excludes loopback,
|
||||
bridges etc.
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- network:
|
||||
name: wlp3s0
|
||||
content:
|
||||
string: {text: "{name}: {state} ({ipv4})"}
|
||||
map:
|
||||
conditions:
|
||||
type == ether || type == wlan:
|
||||
map:
|
||||
default:
|
||||
string: {text: "{name}: {state} ({ipv4})"}
|
||||
conditions:
|
||||
ipv4 == "":
|
||||
string: {text: "{name}: {state}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
|
34
doc/yambar-modules-niri-language.5.scd
Normal file
34
doc/yambar-modules-niri-language.5.scd
Normal file
|
@ -0,0 +1,34 @@
|
|||
yambar-modules-niri-language(5)
|
||||
|
||||
# NAME
|
||||
niri-language - This module provides information about niri's currently
|
||||
selected language.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| language
|
||||
: string
|
||||
: The currently selected language.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- niri-language:
|
||||
content:
|
||||
string: {text: "{language}"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
60
doc/yambar-modules-niri-workspaces.5.scd
Normal file
60
doc/yambar-modules-niri-workspaces.5.scd
Normal file
|
@ -0,0 +1,60 @@
|
|||
yambar-modules-niri-workspaces(5)
|
||||
|
||||
# NAME
|
||||
niri-workspaces - This module provides information about niri workspaces.
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This module provides a map of each workspace present in niri.
|
||||
|
||||
Each workspace has its _id_, _name_, and its status (_focused_,
|
||||
_active_, _empty_). The workspaces are sorted by their ids.
|
||||
|
||||
This module will *only* track the monitor where yambar was launched.
|
||||
If you have a multi monitor setup, please launch yambar on each
|
||||
individual monitor to track its workspaces.
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: The workspace id.
|
||||
| name
|
||||
: string
|
||||
: The name of the workspace.
|
||||
| active
|
||||
: bool
|
||||
: True if the workspace is currently visible on the current output.
|
||||
| focused
|
||||
: bool
|
||||
: True if the workspace is currently focused.
|
||||
| empty
|
||||
: bool
|
||||
: True if the workspace contains no window.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
No additional attributes supported, only the generic ones (see
|
||||
*GENERIC CONFIGURATION* in *yambar-modules*(5))
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- niri-workspaces:
|
||||
content:
|
||||
map:
|
||||
default: {string: {text: "| {id}"}}
|
||||
conditions:
|
||||
active: {string: {text: "-> {id}"}}
|
||||
~empty: {string: {text: "@ {id}"}}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
103
doc/yambar-modules-pipewire.5.scd
Normal file
103
doc/yambar-modules-pipewire.5.scd
Normal file
|
@ -0,0 +1,103 @@
|
|||
yambar-modules-pipewire(5)
|
||||
|
||||
# NAME
|
||||
pipewire - Monitors pipewire for volume, mute/unmute, device change
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| type
|
||||
: string
|
||||
: Either "source" (capture) or "sink" (speaker)
|
||||
| name
|
||||
: string
|
||||
: Current device name
|
||||
| description
|
||||
: string
|
||||
: Current device description
|
||||
| form_factor
|
||||
: string
|
||||
: Current device form factor (headset, speaker, mic, etc.)
|
||||
| bus
|
||||
: string
|
||||
: Current device bus (bluetooth, alsa, etc.)
|
||||
| icon
|
||||
: string
|
||||
: Current device icon name
|
||||
| muted
|
||||
: bool
|
||||
: True if muted, otherwise false
|
||||
| linear_volume
|
||||
: range
|
||||
: Linear volume in percentage (with 0 as min and 100 as max)
|
||||
| cubic_volume
|
||||
: range
|
||||
: Cubic volume (used by pulseaudio) in percentage (with 0 as min and 100 as max)
|
||||
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, in the left side of each rendered volume
|
||||
| right-spacing
|
||||
: int
|
||||
: no
|
||||
: Space, in pixels, on the right side of each rendered volume
|
||||
| spacing
|
||||
: int
|
||||
: no
|
||||
: Short-hand for setting both _left-spacing_ and _right-spacing_
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
: Unlike other modules, _content_ is a template particle that will be
|
||||
expanded twice (i.e. into a list of two elements). The first
|
||||
element is the 'sink', and the second element the 'source'.
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- pipewire:
|
||||
anchors:
|
||||
volume: &volume
|
||||
conditions:
|
||||
muted: {string: {text: "{linear_volume}%", foreground: ff0000ff}}
|
||||
~muted: {string: {text: "{linear_volume}%"}}
|
||||
content:
|
||||
list:
|
||||
items:
|
||||
- map:
|
||||
conditions:
|
||||
type == "sink":
|
||||
map:
|
||||
conditions:
|
||||
icon == "audio-headset-bluetooth":
|
||||
string: {text: "🎧 "}
|
||||
default:
|
||||
- ramp:
|
||||
tag: linear_volume
|
||||
items:
|
||||
- string: {text: "🔈 "}
|
||||
- string: {text: "🔉 "}
|
||||
- string: {text: "🔊 "}
|
||||
type == "source":
|
||||
- string: {text: "🎙 "}
|
||||
- map:
|
||||
<<: *volume
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
67
doc/yambar-modules-pulse.5.scd
Normal file
67
doc/yambar-modules-pulse.5.scd
Normal file
|
@ -0,0 +1,67 @@
|
|||
yambar-modules-pulse(5)
|
||||
|
||||
# NAME
|
||||
pulse - Monitors a PulseAudio source and/or sink
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| online
|
||||
: bool
|
||||
: True when connected to the PulseAudio server
|
||||
| sink_online
|
||||
: bool
|
||||
: True when the sink is present
|
||||
| source_online
|
||||
: bool
|
||||
: True when the source is present
|
||||
| sink_percent
|
||||
: range
|
||||
: Sink volume level, as a percentage
|
||||
| source_percent
|
||||
: range
|
||||
: Source volume level, as a percentage
|
||||
| sink_muted
|
||||
: bool
|
||||
: True if the sink is muted, otherwise false
|
||||
| source_muted
|
||||
: bool
|
||||
: True if the source is muted, otherwise false
|
||||
| sink_port
|
||||
: string
|
||||
: Description of the active sink port
|
||||
| source_port
|
||||
: string
|
||||
: Description of the active source port
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:< *Description*
|
||||
| sink
|
||||
: string
|
||||
: no
|
||||
: Name of sink to monitor (default: _@DEFAULT\_SINK@_).
|
||||
| source
|
||||
: string
|
||||
: no
|
||||
: Name of source to monitor (default: _@DEFAULT\_SOURCE@_).
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- pulse:
|
||||
content:
|
||||
string: {text: "{sink_percent}% ({sink_port})"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
|
@ -12,7 +12,7 @@ instantiates the provided _content_ particle for each detected drive.
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| vendor
|
||||
: string
|
||||
: Name of the drive vendor
|
||||
|
@ -22,6 +22,10 @@ instantiates the provided _content_ particle for each detected drive.
|
|||
| optical
|
||||
: bool
|
||||
: True if the drive is an optical drive (CD-ROM, DVD-ROM etc)
|
||||
| audio
|
||||
: bool
|
||||
: True if an optical drive has an audio CD inserted (i.e. this
|
||||
property is always false for non-optical drives).
|
||||
| device
|
||||
: string
|
||||
: Volume device name (typically */dev/sd?*)
|
||||
|
@ -44,7 +48,7 @@ instantiates the provided _content_ particle for each detected drive.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
|
@ -70,13 +74,12 @@ bar:
|
|||
- removables:
|
||||
content:
|
||||
map:
|
||||
tag: mounted
|
||||
values:
|
||||
false:
|
||||
conditions:
|
||||
~mounted:
|
||||
string:
|
||||
on-click: udisksctl mount -b {device}
|
||||
text: "{label}"
|
||||
true:
|
||||
mounted:
|
||||
string:
|
||||
on-click: udisksctl unmount -b {device}
|
||||
text: "{label}"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
yambar-modules-river(5)
|
||||
|
||||
# NAME
|
||||
river - This module provide information about the river tags
|
||||
river - This module provides information about the river tags
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
|
@ -12,21 +12,25 @@ about the river tags.
|
|||
It has an interface similar to the i3/sway module.
|
||||
|
||||
The configuration for the river module specifies one _title_ particle,
|
||||
which will be instantiated with tags representing the currently active
|
||||
seat and the currently focused view's title.
|
||||
which will be instantiated once for each seat, with tags representing
|
||||
the seats' name, the title of the seats' currently focused view, and
|
||||
its current river "mode".
|
||||
|
||||
It also specifies a _content_ template particle, which is instantiated
|
||||
once for all 32 river tags. This means you probably want to use a
|
||||
*map* particle to hide unused river tags.
|
||||
|
||||
# TAGS
|
||||
# TAGS (for the "content" particle)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| id
|
||||
: int
|
||||
: River tag number
|
||||
| urgent
|
||||
: bool
|
||||
: True if the river tag has at least one urgent view.
|
||||
| visible
|
||||
: bool
|
||||
: True if the river tag is focused by at least one output (i.e. visible on at least one monitor).
|
||||
|
@ -38,20 +42,35 @@ once for all 32 river tags. This means you probably want to use a
|
|||
: True if the river tag has views (i.e. windows).
|
||||
| state
|
||||
: string
|
||||
: Set to *focused* if _focused_ is true, *unfocused* if _visible_ is true, but _focused_ is false, or *invisible* if the river tag is not visible on any monitors.
|
||||
: Set to *urgent* if _urgent_ is true, *focused* if _focused_ is true,
|
||||
*unfocused* if _visible_ is true, but _focused_ is false, or
|
||||
*invisible* if the river tag is not visible on any monitors.
|
||||
|
||||
|
||||
# TAGS (for the "title" particle)
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:< *Description*
|
||||
| seat
|
||||
: string
|
||||
: The name of the currently active seat (*title* particle only, see CONFIGURATION)
|
||||
: The name of the seat.
|
||||
| title
|
||||
: string
|
||||
: The focused view's title (*title* particle only, see CONFIGURATION)
|
||||
: The seat's focused view's title.
|
||||
| mode
|
||||
: string
|
||||
: The seat's current mode (entered with e.g. *riverctl enter-mode foobar*).
|
||||
| layout
|
||||
: string
|
||||
: Current layout of the output currently focused by the seat.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| title
|
||||
: particle
|
||||
: no
|
||||
|
@ -60,6 +79,12 @@ once for all 32 river tags. This means you probably want to use a
|
|||
: particle
|
||||
: yes
|
||||
: Template particle that will be instantiated once for all of the 32 river tags.
|
||||
| all-monitors
|
||||
: bool
|
||||
: no
|
||||
: When set to false (the default), tags reflect river tags and seats
|
||||
for the monitor yambar is on only. When set to true, tags reflect
|
||||
the union of all monitors.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
@ -67,13 +92,12 @@ once for all 32 river tags. This means you probably want to use a
|
|||
bar:
|
||||
left:
|
||||
- river:
|
||||
title: {string: { text: "{seat} - {title}" }}
|
||||
title: {string: { text: "{seat} - {title} ({layout}/{mode})" }}
|
||||
content:
|
||||
map:
|
||||
tag: occupied
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~occupied: {empty: {}}
|
||||
occupied:
|
||||
string:
|
||||
margin: 5
|
||||
text: "{id}: {state}"
|
||||
|
|
|
@ -16,7 +16,7 @@ configurable amount of time.
|
|||
In continuous mode, the script is executed once. It will typically run
|
||||
in a loop, sending an updated tag set whenever it needs, or wants
|
||||
to. The last tag set is used (displayed) by yambar until a new tag set
|
||||
is received. This mode is intended to be used by scripts that depends
|
||||
is received. This mode is intended to be used by scripts that depend
|
||||
on non-polling methods to update their state.
|
||||
|
||||
Tag sets, or _transactions_, are separated by an empty line
|
||||
|
@ -66,19 +66,21 @@ User defined.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| path
|
||||
: string
|
||||
: yes
|
||||
: Path to script/binary to execute. Must be an absolute path.
|
||||
: Path to script/binary to execute. Must either be an absolute path,
|
||||
or start with *~/*.
|
||||
| args
|
||||
: list of strings
|
||||
: no
|
||||
: Arguments to pass to the script/binary.
|
||||
| poll-interval
|
||||
: integer
|
||||
: Number of seconds between each script run. If unset, continuous mode
|
||||
is used.
|
||||
: no
|
||||
: Number of milliseconds between each script run. If unset, or set to
|
||||
0, continuous mode is used.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
@ -113,6 +115,36 @@ bar:
|
|||
content: {string: {text: "{test}"}}
|
||||
```
|
||||
|
||||
Another example use case of this module could be to display currently playing
|
||||
song or other media from players that support MPRIS (Media Player Remote
|
||||
Interfacing Specification):
|
||||
|
||||
```
|
||||
bar:
|
||||
center:
|
||||
- script:
|
||||
path: /usr/bin/playerctl
|
||||
args:
|
||||
- "--follow"
|
||||
- "metadata"
|
||||
- "-f"
|
||||
- |
|
||||
status|string|{{status}}
|
||||
artist|string|{{artist}}
|
||||
title|string|{{title}}
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
status == Paused: {empty: {}}
|
||||
status == Playing:
|
||||
content: {string: {text: "{artist} - {title}"}}
|
||||
```
|
||||
|
||||
The above snippet runs a _playerctl_ utility in _--follow_ mode, reacting to
|
||||
media updates on DBUS and outputting status, artist and title of media being
|
||||
played in a format that is recognized by yambar. See _playerctl_ documentation
|
||||
for more available metadata fields and control over which players get used.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||
|
|
|
@ -16,7 +16,7 @@ instantiated from this template, and represents an input device.
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| id
|
||||
: string
|
||||
: Input device identifier
|
||||
|
@ -29,7 +29,7 @@ instantiated from this template, and represents an input device.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| identifiers
|
||||
: list of strings
|
||||
: yes
|
||||
|
|
|
@ -14,7 +14,7 @@ Note: this module is X11 only. It does not work in Wayland.
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| name
|
||||
: string
|
||||
: Name of currently selected layout, long version (e.g. "English (US)")
|
||||
|
|
|
@ -16,7 +16,7 @@ _title_ tags.
|
|||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| application
|
||||
: string
|
||||
: Name of the application that owns the currently focused window
|
||||
|
|
|
@ -38,7 +38,7 @@ For example, to render _backlight_ as " 20%", you could use:
|
|||
```
|
||||
content:
|
||||
- string:
|
||||
font: Font Awesome 5 Free:style=solid:pixelsize=14
|
||||
font: Font Awesome 6 Free:style=solid:pixelsize=14
|
||||
text:
|
||||
- string:
|
||||
font: Adobe Helvetica:pixelsize=12
|
||||
|
@ -68,20 +68,17 @@ in red.
|
|||
```
|
||||
content:
|
||||
map:
|
||||
tag: carrier
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~carrier: {empty: {}}
|
||||
carrier:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
up:
|
||||
conditions:
|
||||
state == up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
```
|
||||
|
||||
## Use yaml anchors
|
||||
|
@ -94,7 +91,7 @@ In these cases, you can define an anchor point, either at top-level,
|
|||
or in a module's _anchors_ attribute:
|
||||
|
||||
```
|
||||
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
|
||||
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14
|
||||
|
||||
```
|
||||
|
||||
|
@ -113,7 +110,7 @@ following attributes are supported by all modules:
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
|
@ -145,14 +142,28 @@ Available modules have their own pages:
|
|||
|
||||
*yambar-modules-clock*(5)
|
||||
|
||||
*yambar-modules-cpu*(5)
|
||||
|
||||
*yambar-modules-disk-io*(5)
|
||||
|
||||
*yambar-modules-dwl*(5)
|
||||
|
||||
*yambar-modules-foreign-toplevel*(5)
|
||||
|
||||
*yambar-modules-i3*(5)
|
||||
|
||||
*yambar-modules-label*(5)
|
||||
|
||||
*yambar-modules-mem*(5)
|
||||
|
||||
*yambar-modules-mpd*(5)
|
||||
|
||||
*yambar-modules-network*(5)
|
||||
|
||||
*yambar-modules-pipewire*(5)
|
||||
|
||||
*yambar-modules-pulse*(5)
|
||||
|
||||
*yambar-modules-removables*(5)
|
||||
|
||||
*yambar-modules-river*(5)
|
||||
|
@ -163,6 +174,10 @@ Available modules have their own pages:
|
|||
|
||||
*yambar-modules-sway*(5)
|
||||
|
||||
*yambar-modules-niri-language*(5)
|
||||
|
||||
*yambar-modules-niri-workspaces*(5)
|
||||
|
||||
*yambar-modules-xkb*(5)
|
||||
|
||||
*yambar-modules-xwindow*(5)
|
||||
|
|
|
@ -12,7 +12,7 @@ following attributes are supported by all particles:
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| left-margin
|
||||
: int
|
||||
: no
|
||||
|
@ -31,20 +31,76 @@ following attributes are supported by all particles:
|
|||
: Font to use. Note that this is an inherited attribute; i.e. you can
|
||||
set it on e.g. a _list_ particle, and it will apply to all
|
||||
particles in the list.
|
||||
| font-shaping
|
||||
: enum
|
||||
: no
|
||||
: font-shaping; one of _full_ or _none_. When set to _full_ (the
|
||||
default), strings will be "shaped" using HarfBuzz. Requires support
|
||||
in fcft.
|
||||
| foreground
|
||||
: color
|
||||
: no
|
||||
: Foreground (text) color. Just like _font_, this is an inherited attribute.
|
||||
| on-click
|
||||
: associative array/string
|
||||
: no
|
||||
: When set to a string, executes the string as a command when the
|
||||
particle is left-clicked. Tags can be used. Note that the string is
|
||||
*not* executed in a shell. Environment variables are not expanded.
|
||||
*~/* is expanded, but only in the first argument. The same applies
|
||||
to all attributes associated with it, below.
|
||||
| on-click.left
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked. Tags can be
|
||||
used. Note that the string is *not* executed in a shell.
|
||||
: Command to execute when the particle is left-clicked.
|
||||
| on-click.right
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is right-clicked.
|
||||
| on-click.middle
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is middle-clicked.
|
||||
| on-click.wheel-up
|
||||
: string
|
||||
: no
|
||||
: Command to execute every time a 'wheel-up' event is triggered.
|
||||
| on-click.wheel-down
|
||||
: string
|
||||
: no
|
||||
: Command to execute every time a 'wheel-down' event is triggered.
|
||||
| on-click.previous
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked with the 'previous' button.
|
||||
| on-click.next
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked with the 'next' button.
|
||||
| deco
|
||||
: decoration
|
||||
: no
|
||||
: Decoration to apply to the particle. See *yambar-decorations*(5)
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
*on-click* as a string (handles left click):
|
||||
```
|
||||
content:
|
||||
<particle>:
|
||||
on-click: command args
|
||||
```
|
||||
|
||||
*on-click* as an associative array (handles other buttons):
|
||||
```
|
||||
content:
|
||||
<particle>:
|
||||
on-click:
|
||||
left: command-1
|
||||
wheel-up: command-3
|
||||
wheel-down: command-4
|
||||
```
|
||||
|
||||
# STRING
|
||||
|
||||
This is the most basic particle. It takes a format string, consisting
|
||||
|
@ -59,7 +115,7 @@ of free text mixed with tag specifiers.
|
|||
| text
|
||||
: string
|
||||
: yes
|
||||
: Format string. Tags are spcified with _{tag_name}_. Some tag types
|
||||
: Format string. Tags are specified with _{tag_name}_. Some tag types
|
||||
have suffixes that can be appended (e.g. _{tag_name:suffix}_). See
|
||||
*yambar-modules*(5)).
|
||||
| max
|
||||
|
@ -67,9 +123,9 @@ of free text mixed with tag specifiers.
|
|||
: no
|
||||
: Sets the rendered string's maximum length. If the final string's
|
||||
length exceeds this, the rendered string will be truncated, and
|
||||
"..." will be appended. Note that the trailing "..." are
|
||||
"…" will be appended. Note that the trailing "…" is
|
||||
*included* in the maximum length. I.e. if you set _max_ to '5', you
|
||||
will only get *2* characters from the string.
|
||||
will only get *4* characters from the string.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
@ -82,7 +138,7 @@ content:
|
|||
# EMPTY
|
||||
|
||||
This particle is a place-holder. While it does not render any tags,
|
||||
margins and decortions are rendered.
|
||||
margins and decorations are rendered.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
|
@ -99,7 +155,7 @@ content:
|
|||
|
||||
This particle is a list (or sequence, if you like) of other
|
||||
particles. It can be used to render e.g. _string_ particles with
|
||||
different font and/or color formatting. Or ay other particle
|
||||
different font and/or color formatting. Or any other particle
|
||||
combinations.
|
||||
|
||||
But note that this means you *cannot* set any attributes on the _list_
|
||||
|
@ -110,7 +166,7 @@ particle itself.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| items
|
||||
: list
|
||||
: yes
|
||||
|
@ -158,51 +214,165 @@ content:
|
|||
- string: ...
|
||||
```
|
||||
|
||||
Note that the short form has a hard-coded *right-spacing* of 2. This
|
||||
cannot be changed. If you want a different spacing, you must use an
|
||||
explicit list particle (i.e. the long form).
|
||||
|
||||
|
||||
# MAP
|
||||
|
||||
This particle maps the values of a specific tag to different
|
||||
particles. In addition to explicit tag values, you can also specify a
|
||||
particles based on conditions. A condition takes either the form of:
|
||||
|
||||
```
|
||||
<tag> <operation> <value>
|
||||
```
|
||||
|
||||
Or, for boolean tags:
|
||||
|
||||
```
|
||||
<tag>
|
||||
```
|
||||
|
||||
Where <tag> is the tag you would like to map, <operation> is one of:
|
||||
|
||||
[- ==
|
||||
:- !=
|
||||
:- >=
|
||||
:- >
|
||||
:- <=
|
||||
:- <
|
||||
|
||||
and <value> is the value you would like to compare it to. *If the
|
||||
value contains any non-alphanumerical characters, you must
|
||||
surround it with ' \" ' *:
|
||||
|
||||
```
|
||||
"hello world"
|
||||
"@#$%"
|
||||
```
|
||||
|
||||
Negation is done with a preceding '~':
|
||||
|
||||
```
|
||||
~<tag>
|
||||
~<condition>
|
||||
```
|
||||
|
||||
To match for empty strings, use ' "" ':
|
||||
|
||||
```
|
||||
<tag> == ""
|
||||
```
|
||||
|
||||
String glob matching
|
||||
|
||||
To perform string matching using globbing with "\*" & "?" characters:
|
||||
\* Match any zero or more characters. ? Match exactly any one
|
||||
character.
|
||||
|
||||
```
|
||||
<tag> ~~ "hello*"
|
||||
```
|
||||
|
||||
Will match any string starting with "hello", including "hello",
|
||||
"hello1", "hello123", etc.
|
||||
|
||||
```
|
||||
<tag> ~~ "hello?"
|
||||
```
|
||||
|
||||
Will match any string starting with "hello" followed by any single
|
||||
character, including "hello1", "hello-", but not "hello".
|
||||
|
||||
Furthermore, you may use the boolean operators:
|
||||
|
||||
[- &&
|
||||
:- ||
|
||||
|
||||
in order to create more complex conditions:
|
||||
|
||||
```
|
||||
<condition1> && <condition2>
|
||||
```
|
||||
|
||||
You may surround <condition> with parenthesis for clarity or
|
||||
specifying precedence:
|
||||
|
||||
```
|
||||
(<condition>)
|
||||
<condition1> && (<condition2> || <condition3>)
|
||||
```
|
||||
|
||||
In addition to explicit tag values, you can also specify a
|
||||
default/fallback particle.
|
||||
|
||||
Note that conditions are evaluated in the order they appear. *If
|
||||
multiple conditions are true, the first one will be used*. This means
|
||||
that in a configuration such as:
|
||||
|
||||
```
|
||||
tx-bitrate > 1000:
|
||||
tx-bitrate > 1000000:
|
||||
```
|
||||
|
||||
the second condition would never run, since whenever the second
|
||||
condition is true, the first is also true. The correct way of doing
|
||||
this would be to invert the order of the conditions:
|
||||
|
||||
```
|
||||
tx-bitrate > 1000000:
|
||||
tx-bitrate > 1000:
|
||||
```
|
||||
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
: The tag (name of) which values should be mapped
|
||||
| values
|
||||
:< *Description*
|
||||
| conditions
|
||||
: associative array
|
||||
: yes
|
||||
: An associative array of tag values mapped to particles
|
||||
: An associative array of conditions (see above) mapped to particles
|
||||
| default
|
||||
: particle
|
||||
: no
|
||||
: Default particle to use, when tag's value does not match any of the
|
||||
mapped values.
|
||||
: Default particle to use, none of the conditions are true
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
map:
|
||||
tag: tag_name
|
||||
default:
|
||||
string:
|
||||
text: this is the default particle; the tag's value is now {tag_name}
|
||||
values:
|
||||
one_value:
|
||||
conditions:
|
||||
tag == one_value:
|
||||
string:
|
||||
text: tag's value is now one_value
|
||||
another_value:
|
||||
tag == another_value:
|
||||
string:
|
||||
text: tag's value is now another_value
|
||||
|
||||
```
|
||||
|
||||
For a boolean tag:
|
||||
|
||||
```
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
tag:
|
||||
string:
|
||||
text: tag is true
|
||||
~tag:
|
||||
string:
|
||||
text: tag is false
|
||||
```
|
||||
|
||||
# RAMP
|
||||
|
||||
This particle uses a range tag to index into an array of
|
||||
|
@ -215,7 +385,7 @@ indicator.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
|
@ -226,6 +396,18 @@ indicator.
|
|||
: List of particles. Note that the tag value is *not* used as-is; its
|
||||
minimum and maximum values are used to map the tag's range to the
|
||||
particle list's range.
|
||||
| min
|
||||
: int
|
||||
: no
|
||||
: If present this will be used as a lower bound instead of the tags
|
||||
minimum value. Tag values falling outside the defined range will
|
||||
get clamped to min/max.
|
||||
| max
|
||||
: int
|
||||
: no
|
||||
: If present this will be used as an upper bound instead of the tags
|
||||
maximum value. Tag values falling outside the defined range will
|
||||
get clamped to min/max.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
@ -258,7 +440,7 @@ itself when needed.
|
|||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
|
@ -294,7 +476,7 @@ itself when needed.
|
|||
|
||||
```
|
||||
content:
|
||||
progres-bar:
|
||||
progress-bar:
|
||||
tag: tag_name
|
||||
length: 20
|
||||
start: {string: {text: ├}}
|
||||
|
|
|
@ -11,7 +11,7 @@ their information. Each module defines its own set of tags.
|
|||
The available tag *types* are:
|
||||
|
||||
[[ *Type*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| string
|
||||
: Value is a string. Rendered as-is by the _string_ particle.
|
||||
| int
|
||||
|
@ -38,9 +38,97 @@ The available tag *types* are:
|
|||
|
||||
# FORMATTING
|
||||
|
||||
As mentioned above, each tag type has a default representation that is
|
||||
used when the tag is rendered by a string particle.
|
||||
A tag may be followed by one or more formatters that alter the tags
|
||||
rendition.
|
||||
|
||||
All integer, floating point and boolean tag types can be modified to
|
||||
instead be rendered in hexadecimal or octal form, by appending either
|
||||
the *:hex* or *:oct* suffixes. For example, _\"{tag_name:hex}\"_.
|
||||
Formatters are added by appending a ':' separated list of formatter
|
||||
names:
|
||||
|
||||
"{tag_name:max:hex}"
|
||||
|
||||
In the table below, "kind" describes the type of action performed by
|
||||
the formatter:
|
||||
|
||||
- *format*: changes the representation of the tag's value
|
||||
- *selector*: changes what to render
|
||||
|
||||
In general, formatters of the same kind cannot be combined; if
|
||||
multiple formatters of the same kind are specified, the last one will
|
||||
be used.
|
||||
|
||||
[[ *Formatter*
|
||||
:[ *Kind*
|
||||
:[ *Applies to*
|
||||
:< *Description*
|
||||
| [0]<number>[.]
|
||||
: format
|
||||
: Numeric tags (integer and floats)
|
||||
: The width reserved to the field. The leading '0' is optional and
|
||||
indicates zero padding, as opposed to space padding. The trailing
|
||||
'.' is also optional
|
||||
| .<number>
|
||||
: format
|
||||
: Float tags
|
||||
: How many decimals to print
|
||||
| [0]<N>[.]<M>
|
||||
: format
|
||||
: N: numeric tags, M: float tags
|
||||
: Combined version of the two previous formatters
|
||||
| hex
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value in hex
|
||||
| oct
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value in octal
|
||||
| %
|
||||
: format
|
||||
: Range tags
|
||||
: Renders a range tag's value as a percentage value
|
||||
| /N
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value (in decimal) divided by N
|
||||
| kb, mb, gb
|
||||
: format
|
||||
: All tag types
|
||||
: Renders a tag's value (in decimal) divided by 1000, 1000^2 or
|
||||
1000^3. Note: no unit suffix is appended
|
||||
| kib, mib, gib
|
||||
: format
|
||||
: All tag types
|
||||
: Same as *kb*, *mb* and *gb*, but divide by 1024^n instead of 1000^n.
|
||||
| min
|
||||
: selector
|
||||
: Range tags
|
||||
: Renders a range tag's minimum value
|
||||
| max
|
||||
: selector
|
||||
: Range tags
|
||||
: Renders a range tag's maximum value
|
||||
| unit
|
||||
: selector
|
||||
: Realtime tags
|
||||
: Renders a realtime tag's unit (e.g. "s", or "ms")
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
- A numeric (float or int) tag with at least 3 digits, zero-padded if
|
||||
necessary:
|
||||
|
||||
```
|
||||
{tag:03}
|
||||
```
|
||||
|
||||
- A float tag with 2 decimals:
|
||||
|
||||
```
|
||||
{tag:.2}
|
||||
```
|
||||
|
||||
- A "byte count" tag in gigabytes:
|
||||
|
||||
```
|
||||
{tag:gib}GB
|
||||
```
|
||||
|
|
|
@ -25,7 +25,11 @@ yambar - modular status panel for X11 and Wayland
|
|||
*-p*,*--print-pid*=_FILE_|_FD_
|
||||
Print PID to this file, or FD, when successfully started. The file
|
||||
(or FD) is closed immediately after writing the PID. When a _FILE_
|
||||
as been specified, the file is unlinked exit.
|
||||
as been specified, the file is unlinked upon exiting.
|
||||
|
||||
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
|
||||
Log level, used both for log output on stderr as well as
|
||||
syslog. Default: _warning_.
|
||||
|
||||
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
|
||||
Enables or disables colorization of log output on stderr.
|
||||
|
|
|
@ -12,9 +12,10 @@ and reference them using anchors.
|
|||
Besides the normal yaml types, there are a couple of yambar specific
|
||||
types that are frequently used:
|
||||
|
||||
- *font*: this is a string in _fontconfig_ format. Example of valid values:
|
||||
- Font Awesome 5 Brands
|
||||
- Font Awesome 5 Free:style=solid
|
||||
- *font*: this is a comma separated list of fonts in _fontconfig_
|
||||
format. Example of valid values:
|
||||
- Font Awesome 6 Brands
|
||||
- Font Awesome 6 Free:style=solid
|
||||
- Dina:pixelsize=10:slant=italic
|
||||
- Dina:pixelsize=10:weight=bold
|
||||
- *color*: an rgba hexstring; _RRGGBBAA_. Examples:
|
||||
|
@ -22,12 +23,17 @@ types that are frequently used:
|
|||
- 000000ff: black, no transparency
|
||||
- 00ff00ff: green, no transparency
|
||||
- ff000099: red, semi-transparent
|
||||
- *environment reference*: a string that contains format ${VAR}. This will be
|
||||
replaced by the value of the environment variable VAR. Example:
|
||||
- ${HOME}
|
||||
- ${HOME}/.config/yambar
|
||||
- ENV is ${ENV}, ENV2 is ${ENV2}
|
||||
|
||||
# FORMAT
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
:< *Description*
|
||||
| height
|
||||
: int
|
||||
: yes
|
||||
|
@ -45,6 +51,11 @@ types that are frequently used:
|
|||
: no
|
||||
: Monitor to place the bar on. If not specified, the primary monitor will be
|
||||
used
|
||||
| layer
|
||||
: string
|
||||
: no
|
||||
: Layer to put bar on. One of _overlay_, _top_, _bottom_ or
|
||||
_background_. Wayland only. Default: _bottom_.
|
||||
| left-spacing
|
||||
: int
|
||||
: no
|
||||
|
@ -73,10 +84,26 @@ types that are frequently used:
|
|||
: associative array
|
||||
: no
|
||||
: Configures the border around the status bar
|
||||
| border.left-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the left side, in pixels
|
||||
| border.right-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the right side, in pixels
|
||||
| border.top-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the top side, in pixels
|
||||
| border.bottom-width
|
||||
: int
|
||||
: no
|
||||
: Width of the border on the bottom side, in pixels
|
||||
| border.width
|
||||
: int
|
||||
: no
|
||||
: Width, in pixels, of the border
|
||||
: Short-hand for setting _border.left/right/top/bottom-width_
|
||||
| border.color
|
||||
: color
|
||||
: no
|
||||
|
@ -104,7 +131,17 @@ types that are frequently used:
|
|||
| font
|
||||
: font
|
||||
: no
|
||||
: Default font to use in modules and particles
|
||||
: Default font to use in modules and particles. May also be a comma
|
||||
separated list of several fonts, in which case the first font is
|
||||
the primary font, and the rest fallback fonts. These are yambar
|
||||
custom fallback fonts that will be searched before the fontconfig
|
||||
provided fallback list.
|
||||
| font-shaping
|
||||
: enum
|
||||
: no
|
||||
: Default setting for font-shaping, for use in particles. One of
|
||||
_full_ or _none_. When set to _full_ (the default), strings will be
|
||||
"shaped" using HarfBuzz. Requires support in fcft.
|
||||
| foreground
|
||||
: color
|
||||
: no
|
||||
|
@ -141,8 +178,8 @@ bar:
|
|||
|
||||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: "{time}"}
|
||||
content:
|
||||
- string: {text: "{time}"}
|
||||
```
|
||||
|
||||
# FILES
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
# For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and
|
||||
# the sway-xkb module with the xkb module.
|
||||
|
||||
# fonts we'll be re-using here and there
|
||||
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
|
||||
awesome_brands: &awesome_brands Font Awesome 5 Brands:pixelsize=16
|
||||
# fonts we'll be reusing here and there
|
||||
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14
|
||||
awesome_brands: &awesome_brands Font Awesome 6 Brands:pixelsize=16
|
||||
|
||||
std_underline: &std_underline {underline: { size: 2, color: ff0000ff}}
|
||||
|
||||
|
@ -46,82 +46,67 @@ bar:
|
|||
foreground: 000000ff
|
||||
deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]}
|
||||
- map: &i3_mode
|
||||
tag: mode
|
||||
default:
|
||||
- string:
|
||||
margin: 5
|
||||
text: "{mode}"
|
||||
deco: {background: {color: cc421dff}}
|
||||
- empty: {right-margin: 7}
|
||||
values:
|
||||
default: {empty: {}}
|
||||
conditions:
|
||||
mode == default: {empty: {}}
|
||||
content:
|
||||
"":
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*default, *focused]}}
|
||||
unfocused: {string: {<<: *default}}
|
||||
invisible: {string: {<<: [*default, *invisible]}}
|
||||
urgent: {string: {<<: [*default, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*default, *focused]}}
|
||||
state == unfocused: {string: {<<: *default}}
|
||||
state == invisible: {string: {<<: [*default, *invisible]}}
|
||||
state == urgent: {string: {<<: [*default, *urgent]}}
|
||||
main:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*main, *focused]}}
|
||||
unfocused: {string: {<<: *main}}
|
||||
invisible: {string: {<<: [*main, *invisible]}}
|
||||
urgent: {string: {<<: [*main, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*main, *focused]}}
|
||||
state == unfocused: {string: {<<: *main}}
|
||||
state == invisible: {string: {<<: [*main, *invisible]}}
|
||||
state == urgent: {string: {<<: [*main, *urgent]}}
|
||||
surfing:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*surfing, *focused]}}
|
||||
unfocused: {string: {<<: *surfing}}
|
||||
invisible: {string: {<<: [*surfing, *invisible]}}
|
||||
urgent: {string: {<<: [*surfing, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*surfing, *focused]}}
|
||||
state == unfocused: {string: {<<: *surfing}}
|
||||
state == invisible: {string: {<<: [*surfing, *invisible]}}
|
||||
state == urgent: {string: {<<: [*surfing, *urgent]}}
|
||||
misc:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*misc, *focused]}}
|
||||
unfocused: {string: {<<: *misc}}
|
||||
invisible: {string: {<<: [*misc, *invisible]}}
|
||||
urgent: {string: {<<: [*misc, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*misc, *focused]}}
|
||||
state == unfocused: {string: {<<: *misc}}
|
||||
state == invisible: {string: {<<: [*misc, *invisible]}}
|
||||
state == urgent: {string: {<<: [*misc, *urgent]}}
|
||||
|
||||
mail:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*mail, *focused]}}
|
||||
unfocused: {string: {<<: *mail}}
|
||||
invisible: {string: {<<: [*mail, *invisible]}}
|
||||
urgent: {string: {<<: [*mail, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*mail, *focused]}}
|
||||
state == unfocused: {string: {<<: *mail}}
|
||||
state == invisible: {string: {<<: [*mail, *invisible]}}
|
||||
state == urgent: {string: {<<: [*mail, *urgent]}}
|
||||
music:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*music, *focused]}}
|
||||
unfocused: {string: {<<: *music}}
|
||||
invisible: {string: {<<: [*music, *invisible]}}
|
||||
urgent: {string: {<<: [*music, *urgent]}}
|
||||
current:
|
||||
map:
|
||||
left-margin: 7
|
||||
tag: application
|
||||
values:
|
||||
"":
|
||||
- map: {<<: *i3_mode}
|
||||
- string: {text: "{title}"}
|
||||
default:
|
||||
list:
|
||||
spacing: 0
|
||||
items:
|
||||
- map: {<<: *i3_mode}
|
||||
- string: {text: "{application}", max: 10, foreground: ffa0a0ff}
|
||||
- string: {text: ": "}
|
||||
- string: {text: "{title}", max: 35}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*music, *focused]}}
|
||||
state == unfocused: {string: {<<: *music}}
|
||||
state == invisible: {string: {<<: [*music, *invisible]}}
|
||||
state == urgent: {string: {<<: [*music, *urgent]}}
|
||||
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: "{app-id}", foreground: ffa0a0ff}
|
||||
- string: {text: ": {title}"}
|
||||
center:
|
||||
- mpd:
|
||||
host: /run/mpd/socket
|
||||
|
@ -130,32 +115,28 @@ bar:
|
|||
spacing: 0
|
||||
items:
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{artist}"}}
|
||||
paused: {string: {text: "{artist}", foreground: ffffff66}}
|
||||
conditions:
|
||||
state == playing: {string: {text: "{artist}"}}
|
||||
state == paused: {string: {text: "{artist}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{album}"}}
|
||||
paused: {string: {text: "{album}", foreground: ffffff66}}
|
||||
conditions:
|
||||
state == playing: {string: {text: "{album}"}}
|
||||
state == paused: {string: {text: "{album}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{title}", foreground: ffa0a0ff}}
|
||||
paused: {string: {text: "{title}", foreground: ffffff66}}
|
||||
conditions:
|
||||
state == playing: {string: {text: "{title}", foreground: ffa0a0ff}}
|
||||
state == paused: {string: {text: "{title}", foreground: ffffff66}}
|
||||
|
||||
content:
|
||||
map:
|
||||
margin: 10
|
||||
tag: state
|
||||
values:
|
||||
offline: {string: {text: offline, foreground: ff0000ff}}
|
||||
stopped: {string: {text: stopped}}
|
||||
paused: {list: *artist_album_title}
|
||||
playing: {list: *artist_album_title}
|
||||
conditions:
|
||||
state == offline: {string: {text: offline, foreground: ff0000ff}}
|
||||
state == stopped: {string: {text: stopped}}
|
||||
state == paused: {list: *artist_album_title}
|
||||
state == playing: {list: *artist_album_title}
|
||||
|
||||
right:
|
||||
- removables:
|
||||
|
@ -165,24 +146,21 @@ bar:
|
|||
spacing: 5
|
||||
content:
|
||||
map:
|
||||
tag: mounted
|
||||
values:
|
||||
false:
|
||||
conditions:
|
||||
~mounted:
|
||||
map:
|
||||
tag: optical
|
||||
on-click: udisksctl mount -b {device}
|
||||
values:
|
||||
false: [{string: *drive}, {string: {text: "{label}"}}]
|
||||
true: [{string: *optical}, {string: {text: "{label}"}}]
|
||||
true:
|
||||
conditions:
|
||||
~optical: [{string: *drive}, {string: {text: "{label}"}}]
|
||||
optical: [{string: *optical}, {string: {text: "{label}"}}]
|
||||
mounted:
|
||||
map:
|
||||
tag: optical
|
||||
on-click: udisksctl unmount -b {device}
|
||||
values:
|
||||
false:
|
||||
conditions:
|
||||
~optical:
|
||||
- string: {<<: *drive, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
true:
|
||||
optical:
|
||||
- string: {<<: *optical, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
- sway-xkb:
|
||||
|
@ -191,66 +169,69 @@ bar:
|
|||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{layout}"}
|
||||
- network:
|
||||
name: enp1s0
|
||||
content:
|
||||
map:
|
||||
tag: carrier
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
default: {empty: {}}
|
||||
conditions:
|
||||
name == enp1s0:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
up:
|
||||
conditions:
|
||||
~carrier: {empty: {}}
|
||||
carrier:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
state == up && ipv4 != "": {string: {text: , font: *awesome}}
|
||||
- network:
|
||||
name: wlp2s0
|
||||
poll-interval: 1000
|
||||
content:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
down: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
up:
|
||||
default: {empty: {}}
|
||||
conditions:
|
||||
name == wlp2s0:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
state == down: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
state == up:
|
||||
map:
|
||||
default:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s"}
|
||||
|
||||
conditions:
|
||||
ipv4 == "":
|
||||
- string: {text: , font: *awesome, foreground: ffffff66}
|
||||
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s", foreground: ffffff66}
|
||||
- alsa:
|
||||
card: hw:PCH
|
||||
mixer: Master
|
||||
content:
|
||||
map:
|
||||
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
|
||||
tag: muted
|
||||
values:
|
||||
true: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
false:
|
||||
ramp:
|
||||
tag: volume
|
||||
items:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
conditions:
|
||||
~online: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
online:
|
||||
map:
|
||||
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
|
||||
conditions:
|
||||
muted: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
~muted:
|
||||
ramp:
|
||||
tag: percent
|
||||
items:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- backlight:
|
||||
name: intel_backlight
|
||||
content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}]
|
||||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30
|
||||
content:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
discharging:
|
||||
poll-interval: 30000
|
||||
anchors:
|
||||
discharging: &discharging
|
||||
list:
|
||||
items:
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
|
@ -265,13 +246,20 @@ bar:
|
|||
- string: {text: , font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
charging:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
state == unknown:
|
||||
<<: *discharging
|
||||
state == discharging:
|
||||
<<: *discharging
|
||||
state == charging:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
full:
|
||||
state == full:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% full"}
|
||||
not charging:
|
||||
state == "not charging":
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
|
@ -296,6 +284,6 @@ bar:
|
|||
- label:
|
||||
content:
|
||||
string:
|
||||
on-click: loginctl poweroff
|
||||
on-click: systemctl poweroff
|
||||
text:
|
||||
font: *awesome
|
||||
|
|
58
examples/configurations/river-tags.conf
Normal file
58
examples/configurations/river-tags.conf
Normal file
|
@ -0,0 +1,58 @@
|
|||
hack: &hack Hack Nerd Font:pixelsize=13
|
||||
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
|
||||
bar:
|
||||
height: 40
|
||||
location: top
|
||||
font: JuliaMono:pixelsize=10
|
||||
spacing: 2
|
||||
margin: 0
|
||||
layer: bottom
|
||||
foreground: eeeeeeff
|
||||
background: 2E3440dd
|
||||
|
||||
left:
|
||||
- river:
|
||||
anchors:
|
||||
- base: &river_base
|
||||
left-margin: 10
|
||||
right-margin: 13
|
||||
default: {string: {text: , font: *hack}}
|
||||
conditions:
|
||||
id == 1: {string: {text: ﳐ, font: *hack}}
|
||||
id == 2: {string: {text: , font: *hack}}
|
||||
id == 3: {string: {text: , font: *hack}}
|
||||
id == 4: {string: {text: , font: *hack}}
|
||||
id == 5: {string: {text: , font: *hack}}
|
||||
id == 10: {string: {text: "scratchpad", font: *hack}}
|
||||
id == 11: {string: {text: "work", font: *hack}}
|
||||
|
||||
content:
|
||||
map:
|
||||
on-click:
|
||||
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
|
||||
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
|
||||
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
|
||||
conditions:
|
||||
state == urgent:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {background: {color: D08770ff}}
|
||||
state == focused:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == visible && ~occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == visible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == unfocused:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == invisible && ~occupied: {empty: {}}
|
||||
state == invisible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {underline: {size: 3, color: ea6962ff}}
|
69
examples/river-minimal.yml
Normal file
69
examples/river-minimal.yml
Normal file
|
@ -0,0 +1,69 @@
|
|||
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
|
||||
bar:
|
||||
height: 32
|
||||
location: top
|
||||
background: 000000ff
|
||||
font: NotoSans:pixelsize=16
|
||||
|
||||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{date}", right-margin: 5}
|
||||
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
|
||||
- string: {text: "{time} "}
|
||||
left:
|
||||
- river:
|
||||
anchors:
|
||||
- base: &river_base
|
||||
left-margin: 10
|
||||
right-margin: 13
|
||||
default: {string: {text: }}
|
||||
conditions:
|
||||
id == 1: {string: {text: 1}}
|
||||
id == 2: {string: {text: 2}}
|
||||
id == 3: {string: {text: 3}}
|
||||
id == 4: {string: {text: 4}}
|
||||
id == 5: {string: {text: 5}}
|
||||
|
||||
content:
|
||||
map:
|
||||
on-click:
|
||||
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
|
||||
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
|
||||
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
|
||||
conditions:
|
||||
state == urgent:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {background: {color: D08770ff}}
|
||||
state == focused:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == visible && ~occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == visible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: *bg_default
|
||||
state == unfocused:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == invisible && ~occupied: {empty: {}}
|
||||
state == invisible && occupied:
|
||||
map:
|
||||
<<: *river_base
|
||||
deco: {underline: {size: 3, color: ea6962ff}}
|
||||
|
||||
|
||||
center:
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: " {app-id}", foreground: ffa0a0ff}
|
||||
- string: {text: ": {title}"}
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
# Now the fun part
|
||||
#
|
||||
# Exemple configuration:
|
||||
# Example configuration:
|
||||
#
|
||||
# - script:
|
||||
# path: /absolute/path/to/dwl-tags.sh
|
||||
|
@ -31,39 +31,33 @@
|
|||
# content:
|
||||
# - map:
|
||||
# margin: 4
|
||||
# tag: tag_0_occupied
|
||||
# values:
|
||||
# true:
|
||||
# conditions:
|
||||
# tag_0_occupied:
|
||||
# map:
|
||||
# tag: tag_0_focused
|
||||
# values:
|
||||
# true: {string: {text: "{tag_0}", <<: *focused}}
|
||||
# false: {string: {text: "{tag_0}", <<: *occupied}}
|
||||
# false:
|
||||
# conditions:
|
||||
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}}
|
||||
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *occupied}}
|
||||
# ~tag_0_occupied:
|
||||
# map:
|
||||
# tag: tag_0_focused
|
||||
# values:
|
||||
# true: {string: {text: "{tag_0}", <<: *focused}}
|
||||
# false: {string: {text: "{tag_0}", <<: *default}}
|
||||
# conditions:
|
||||
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}}
|
||||
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *default}}
|
||||
# ...
|
||||
# ...
|
||||
# ...
|
||||
# ...
|
||||
# - map:
|
||||
# margin: 4
|
||||
# tag: tag_8_occupied
|
||||
# values:
|
||||
# true:
|
||||
# conditions:
|
||||
# tag_8_occupied:
|
||||
# map:
|
||||
# tag: tag_8_focused
|
||||
# values:
|
||||
# true: {string: {text: "{tag_8}", <<: *focused}}
|
||||
# false: {string: {text: "{tag_8}", <<: *occupied}}
|
||||
# false:
|
||||
# conditions:
|
||||
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
|
||||
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *occupied}}
|
||||
# ~tag_8_occupied:
|
||||
# map:
|
||||
# tag: tag_8_focused
|
||||
# values:
|
||||
# true: {string: {text: "{tag_8}", <<: *focused}}
|
||||
# false: {string: {text: "{tag_8}", <<: *default}}
|
||||
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
|
||||
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *default}}
|
||||
# - list:
|
||||
# spacing: 3
|
||||
# items:
|
||||
|
@ -127,7 +121,7 @@ while true; do
|
|||
inotifywait -qq --event modify "${fname}"
|
||||
|
||||
# Get info from the file
|
||||
output="$(tail -n4 "${fname}")"
|
||||
output="$(tail -n6 "${fname}")"
|
||||
title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )"
|
||||
#selmon="$(echo "${output}" | grep 'selmon')"
|
||||
layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )"
|
||||
|
@ -142,4 +136,3 @@ done
|
|||
|
||||
unset -v output title layout activetags selectedtags
|
||||
unset -v tags name
|
||||
|
||||
|
|
|
@ -12,22 +12,21 @@
|
|||
# {aur} int number of aur packages
|
||||
# {pkg} int sum of both
|
||||
#
|
||||
# Exemples configuration:
|
||||
# Examples configuration:
|
||||
# - script:
|
||||
# path: /absolute/path/to/pacman.sh
|
||||
# args: []
|
||||
# args: []
|
||||
# content: { string: { text: "{pacman} + {aur} = {pkg}" } }
|
||||
#
|
||||
# To display a message when there is no update:
|
||||
# - script:
|
||||
# path: /absolute/path/to/pacman.sh
|
||||
# args: []
|
||||
# args: []
|
||||
# content:
|
||||
# map:
|
||||
# tag: pkg
|
||||
# default: { string: { text: "{pacman} + {aur} = {pkg}" } }
|
||||
# values:
|
||||
# 0: {string: {text: no updates}}
|
||||
# conditions:
|
||||
# pkg == 0: {string: {text: no updates}}
|
||||
|
||||
|
||||
declare interval aur_helper pacman_num aur_num pkg_num
|
||||
|
@ -48,9 +47,9 @@ while true; do
|
|||
# Change interval
|
||||
# NUMBER[SUFFIXE]
|
||||
# Possible suffix:
|
||||
# "s" seconds / "m" minutes / "h" hours / "d" days
|
||||
# "s" seconds / "m" minutes / "h" hours / "d" days
|
||||
interval="1h"
|
||||
|
||||
|
||||
# Change your aur manager
|
||||
aur_helper="paru"
|
||||
|
||||
|
@ -63,7 +62,7 @@ while true; do
|
|||
else
|
||||
aur_num=$("${aur_helper}" -Qmu | wc -l)
|
||||
fi
|
||||
|
||||
|
||||
pkg_num=$(( pacman_num + aur_num ))
|
||||
|
||||
printf -- '%s\n' "pacman|int|${pacman_num}"
|
||||
|
@ -77,4 +76,3 @@ done
|
|||
|
||||
unset -v interval aur_helper pacman_num aur_num pkg_num
|
||||
unset -f _err
|
||||
|
||||
|
|
42
external/river-status-unstable-v1.xml
vendored
42
external/river-status-unstable-v1.xml
vendored
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_status_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 Isaac Freund
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -16,7 +16,7 @@
|
|||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zriver_status_manager_v1" version="1">
|
||||
<interface name="zriver_status_manager_v1" version="4">
|
||||
<description summary="manage river status objects">
|
||||
A global factory for objects that receive status information specific
|
||||
to river. It could be used to implement, for example, a status bar.
|
||||
|
@ -47,7 +47,7 @@
|
|||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_output_status_v1" version="1">
|
||||
<interface name="zriver_output_status_v1" version="4">
|
||||
<description summary="track output tags and focus">
|
||||
This interface allows clients to receive information about the current
|
||||
windowing state of an output.
|
||||
|
@ -75,12 +75,36 @@
|
|||
</description>
|
||||
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
|
||||
</event>
|
||||
|
||||
<event name="urgent_tags" since="2">
|
||||
<description summary="tags of the output with an urgent view">
|
||||
Sent once on binding the interface and again whenever the set of
|
||||
tags with at least one urgent view changes.
|
||||
</description>
|
||||
<arg name="tags" type="uint" summary="32-bit bitfield"/>
|
||||
</event>
|
||||
|
||||
<event name="layout_name" since="4">
|
||||
<description summary="name of the layout">
|
||||
Sent once on binding the interface should a layout name exist and again
|
||||
whenever the name changes.
|
||||
</description>
|
||||
<arg name="name" type="string" summary="layout name"/>
|
||||
</event>
|
||||
|
||||
<event name="layout_name_clear" since="4">
|
||||
<description summary="name of the layout">
|
||||
Sent when the current layout name has been removed without a new one
|
||||
being set, for example when the active layout generator disconnects.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_seat_status_v1" version="1">
|
||||
<interface name="zriver_seat_status_v1" version="3">
|
||||
<description summary="track seat focus">
|
||||
This interface allows clients to receive information about the current
|
||||
focus of a seat.
|
||||
focus of a seat. Note that (un)focused_output events will only be sent
|
||||
if the client has bound the relevant wl_output globals.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
|
@ -112,5 +136,13 @@
|
|||
</description>
|
||||
<arg name="title" type="string" summary="title of the focused view"/>
|
||||
</event>
|
||||
|
||||
<event name="mode" since="3">
|
||||
<description summary="the active mode changed">
|
||||
Sent once on binding the interface and again whenever a new mode
|
||||
is entered (e.g. with riverctl enter-mode foobar).
|
||||
</description>
|
||||
<arg name="name" type="string" summary="name of the mode"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
270
external/wlr-foreign-toplevel-management-unstable-v1.xml
vendored
Normal file
270
external/wlr-foreign-toplevel-management-unstable-v1.xml
vendored
Normal file
|
@ -0,0 +1,270 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Ilia Bozhinov
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
|
||||
<description summary="list and control opened apps">
|
||||
The purpose of this protocol is to enable the creation of taskbars
|
||||
and docks by providing them with a list of opened applications and
|
||||
letting them request certain actions on them, like maximizing, etc.
|
||||
|
||||
After a client binds the zwlr_foreign_toplevel_manager_v1, each opened
|
||||
toplevel window will be sent via the toplevel event
|
||||
</description>
|
||||
|
||||
<event name="toplevel">
|
||||
<description summary="a toplevel has been created">
|
||||
This event is emitted whenever a new toplevel window is created. It
|
||||
is emitted for all toplevels, regardless of the app that has created
|
||||
them.
|
||||
|
||||
All initial details of the toplevel(title, app_id, states, etc.) will
|
||||
be sent immediately after this event via the corresponding events in
|
||||
zwlr_foreign_toplevel_handle_v1.
|
||||
</description>
|
||||
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
Indicates the client no longer wishes to receive events for new toplevels.
|
||||
However the compositor may emit further toplevel_created events, until
|
||||
the finished event is emitted.
|
||||
|
||||
The client must not send any more requests after this one.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the compositor has finished with the toplevel manager">
|
||||
This event indicates that the compositor is done sending events to the
|
||||
zwlr_foreign_toplevel_manager_v1. The server will destroy the object
|
||||
immediately after sending this request, so it will become invalid and
|
||||
the client should free any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
|
||||
<description summary="an opened toplevel">
|
||||
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
|
||||
window. Each app may have multiple opened toplevels.
|
||||
|
||||
Each toplevel has a list of outputs it is visible on, conveyed to the
|
||||
client with the output_enter and output_leave events.
|
||||
</description>
|
||||
|
||||
<event name="title">
|
||||
<description summary="title change">
|
||||
This event is emitted whenever the title of the toplevel changes.
|
||||
</description>
|
||||
<arg name="title" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="app_id">
|
||||
<description summary="app-id change">
|
||||
This event is emitted whenever the app-id of the toplevel changes.
|
||||
</description>
|
||||
<arg name="app_id" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="toplevel entered an output">
|
||||
This event is emitted whenever the toplevel becomes visible on
|
||||
the given output. A toplevel may be visible on multiple outputs.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="toplevel left an output">
|
||||
This event is emitted whenever the toplevel stops being visible on
|
||||
the given output. It is guaranteed that an entered-output event
|
||||
with the same output has been emitted before this event.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<request name="set_maximized">
|
||||
<description summary="requests that the toplevel be maximized">
|
||||
Requests that the toplevel be maximized. If the maximized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="unset_maximized">
|
||||
<description summary="requests that the toplevel be unmaximized">
|
||||
Requests that the toplevel be unmaximized. If the maximized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_minimized">
|
||||
<description summary="requests that the toplevel be minimized">
|
||||
Requests that the toplevel be minimized. If the minimized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="unset_minimized">
|
||||
<description summary="requests that the toplevel be unminimized">
|
||||
Requests that the toplevel be unminimized. If the minimized state actually
|
||||
changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the toplevel">
|
||||
Request that this toplevel be activated on the given seat.
|
||||
There is no guarantee the toplevel will be actually activated.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the toplevel">
|
||||
The different states that a toplevel can have. These have the same meaning
|
||||
as the states with the same names defined in xdg-toplevel
|
||||
</description>
|
||||
|
||||
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
|
||||
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
|
||||
<entry name="activated" value="2" summary="the toplevel is active"/>
|
||||
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
|
||||
</enum>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the toplevel state changed">
|
||||
This event is emitted immediately after the zlw_foreign_toplevel_handle_v1
|
||||
is created and each time the toplevel state changes, either because of a
|
||||
compositor action or because of a request in this protocol.
|
||||
</description>
|
||||
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the toplevel has been sent">
|
||||
This event is sent after all changes in the toplevel state have been
|
||||
sent.
|
||||
|
||||
This allows changes to the zwlr_foreign_toplevel_handle_v1 properties
|
||||
to be seen as atomic, even if they happen via multiple events.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="close">
|
||||
<description summary="request that the toplevel be closed">
|
||||
Send a request to the toplevel to close itself. The compositor would
|
||||
typically use a shell-specific method to carry out this request, for
|
||||
example by sending the xdg_toplevel.close event. However, this gives
|
||||
no guarantees the toplevel will actually be destroyed. If and when
|
||||
this happens, the zwlr_foreign_toplevel_handle_v1.closed event will
|
||||
be emitted.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="set_rectangle">
|
||||
<description summary="the rectangle which represents the toplevel">
|
||||
The rectangle of the surface specified in this request corresponds to
|
||||
the place where the app using this protocol represents the given toplevel.
|
||||
It can be used by the compositor as a hint for some operations, e.g
|
||||
minimizing. The client is however not required to set this, in which
|
||||
case the compositor is free to decide some default value.
|
||||
|
||||
If the client specifies more than one rectangle, only the last one is
|
||||
considered.
|
||||
|
||||
The dimensions are given in surface-local coordinates.
|
||||
Setting width=height=0 removes the already-set rectangle.
|
||||
</description>
|
||||
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="x" type="int"/>
|
||||
<arg name="y" type="int"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_rectangle" value="0"
|
||||
summary="the provided rectangle is invalid"/>
|
||||
</enum>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="this toplevel has been destroyed">
|
||||
This event means the toplevel has been destroyed. It is guaranteed there
|
||||
won't be any more events for this zwlr_foreign_toplevel_handle_v1. The
|
||||
toplevel itself becomes inert so any requests will be ignored except the
|
||||
destroy request.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
|
||||
Destroys the zwlr_foreign_toplevel_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the toplevel anymore or after the closed event to finalize the
|
||||
destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_fullscreen" since="2">
|
||||
<description summary="request that the toplevel be fullscreened">
|
||||
Requests that the toplevel be fullscreened on the given output. If the
|
||||
fullscreen state and/or the outputs the toplevel is visible on actually
|
||||
change, this will be indicated by the state and output_enter/leave
|
||||
events.
|
||||
|
||||
The output parameter is only a hint to the compositor. Also, if output
|
||||
is NULL, the compositor should decide which output the toplevel will be
|
||||
fullscreened on, if at all.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
</request>
|
||||
|
||||
<request name="unset_fullscreen" since="2">
|
||||
<description summary="request that the toplevel be unfullscreened">
|
||||
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||
actually changes, this will be indicated by the state event.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<event name="parent" since="3">
|
||||
<description summary="parent change">
|
||||
This event is emitted whenever the parent of the toplevel changes.
|
||||
|
||||
No event is emitted when the parent handle is destroyed by the client.
|
||||
</description>
|
||||
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
7
font-shaping.h
Normal file
7
font-shaping.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
enum font_shaping {
|
||||
FONT_SHAPE_NONE,
|
||||
FONT_SHAPE_GRAPHEMES,
|
||||
FONT_SHAPE_FULL,
|
||||
};
|
|
@ -13,11 +13,18 @@ out_file=${3}
|
|||
if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then
|
||||
workdir=$(pwd)
|
||||
cd "${src_dir}"
|
||||
git_version=$(git describe --always --tags)
|
||||
|
||||
if git describe --tags > /dev/null 2>&1; then
|
||||
git_version=$(git describe --always --tags)
|
||||
else
|
||||
# No tags available, happens in e.g. CI builds
|
||||
git_version="${default_version}"
|
||||
fi
|
||||
|
||||
git_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
cd "${workdir}"
|
||||
|
||||
new_version="${git_version} ($(env LC_TIME=C date "+%b %d %Y"), branch '${git_branch}')"
|
||||
new_version="${git_version} ($(date "+%b %d %Y"), branch '${git_branch}')"
|
||||
else
|
||||
new_version="${default_version}"
|
||||
fi
|
||||
|
|
258
log.c
258
log.c
|
@ -1,41 +1,60 @@
|
|||
#include "log.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <syslog.h>
|
||||
#define ALEN(v) (sizeof(v) / sizeof((v)[0]))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
static bool colorize = false;
|
||||
static bool do_syslog = true;
|
||||
static bool do_syslog = false;
|
||||
static enum log_class log_level = LOG_CLASS_NONE;
|
||||
|
||||
static const struct {
|
||||
const char name[8];
|
||||
const char log_prefix[7];
|
||||
uint8_t color;
|
||||
int syslog_equivalent;
|
||||
} log_level_map[] = {
|
||||
[LOG_CLASS_NONE] = {"none", "none", 5, -1},
|
||||
[LOG_CLASS_ERROR] = {"error", " err", 31, LOG_ERR},
|
||||
[LOG_CLASS_WARNING] = {"warning", "warn", 33, LOG_WARNING},
|
||||
[LOG_CLASS_INFO] = {"info", "info", 97, LOG_INFO},
|
||||
[LOG_CLASS_DEBUG] = {"debug", " dbg", 36, LOG_DEBUG},
|
||||
};
|
||||
|
||||
void
|
||||
log_init(enum log_colorize _colorize, bool _do_syslog,
|
||||
enum log_facility syslog_facility, enum log_class syslog_level)
|
||||
log_init(enum log_colorize _colorize, bool _do_syslog, enum log_facility syslog_facility, enum log_class _log_level)
|
||||
{
|
||||
static const int facility_map[] = {
|
||||
[LOG_FACILITY_USER] = LOG_USER,
|
||||
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
|
||||
};
|
||||
|
||||
static const int level_map[] = {
|
||||
[LOG_CLASS_ERROR] = LOG_ERR,
|
||||
[LOG_CLASS_WARNING] = LOG_WARNING,
|
||||
[LOG_CLASS_INFO] = LOG_INFO,
|
||||
[LOG_CLASS_DEBUG] = LOG_DEBUG,
|
||||
};
|
||||
/* Don't use colors if NO_COLOR is defined and not empty */
|
||||
const char *no_color_str = getenv("NO_COLOR");
|
||||
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
|
||||
|
||||
colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO);
|
||||
colorize = _colorize == LOG_COLORIZE_NEVER
|
||||
? false
|
||||
: _colorize == LOG_COLORIZE_ALWAYS
|
||||
? true
|
||||
: !no_color && isatty(STDERR_FILENO);
|
||||
do_syslog = _do_syslog;
|
||||
log_level = _log_level;
|
||||
|
||||
if (do_syslog) {
|
||||
openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]);
|
||||
setlogmask(LOG_UPTO(level_map[syslog_level]));
|
||||
int slvl = log_level_map[_log_level].syslog_equivalent;
|
||||
if (do_syslog && slvl != -1) {
|
||||
openlog(NULL, /*LOG_PID*/ 0, facility_map[syslog_facility]);
|
||||
setlogmask(LOG_UPTO(slvl));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,120 +66,153 @@ log_deinit(void)
|
|||
}
|
||||
|
||||
static void
|
||||
_log(enum log_class log_class, const char *module, const char *file, int lineno,
|
||||
const char *fmt, int sys_errno, va_list va)
|
||||
_log(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, int sys_errno,
|
||||
va_list va)
|
||||
{
|
||||
const char *class = "abcd";
|
||||
int class_clr = 0;
|
||||
switch (log_class) {
|
||||
case LOG_CLASS_ERROR: class = " err"; class_clr = 31; break;
|
||||
case LOG_CLASS_WARNING: class = "warn"; class_clr = 33; break;
|
||||
case LOG_CLASS_INFO: class = "info"; class_clr = 97; break;
|
||||
case LOG_CLASS_DEBUG: class = " dbg"; class_clr = 36; break;
|
||||
}
|
||||
assert(log_class > LOG_CLASS_NONE);
|
||||
assert(log_class < ALEN(log_level_map));
|
||||
|
||||
if (log_class > log_level)
|
||||
return;
|
||||
|
||||
const char *prefix = log_level_map[log_class].log_prefix;
|
||||
unsigned int class_clr = log_level_map[log_class].color;
|
||||
|
||||
char clr[16];
|
||||
snprintf(clr, sizeof(clr), "\e[%dm", class_clr);
|
||||
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", class, colorize ? "\e[0m" : "");
|
||||
snprintf(clr, sizeof(clr), "\033[%um", class_clr);
|
||||
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", prefix, colorize ? "\033[0m" : "");
|
||||
|
||||
if (colorize)
|
||||
fprintf(stderr, "\e[2m");
|
||||
fputs("\033[2m", stderr);
|
||||
fprintf(stderr, "%s:%d: ", file, lineno);
|
||||
if (colorize)
|
||||
fprintf(stderr, "\e[0m");
|
||||
fputs("\033[0m", stderr);
|
||||
|
||||
vfprintf(stderr, fmt, va);
|
||||
|
||||
if (sys_errno != 0)
|
||||
fprintf(stderr, ": %s", strerror(sys_errno));
|
||||
fprintf(stderr, ": %s (%d)", strerror(sys_errno), sys_errno);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
static void
|
||||
_sys_log(enum log_class log_class, const char *module,
|
||||
const char *file __attribute__((unused)),
|
||||
int lineno __attribute__((unused)),
|
||||
const char *fmt, int sys_errno, va_list va)
|
||||
_sys_log(enum log_class log_class, const char *module, const char UNUSED *file, int UNUSED lineno, const char *fmt,
|
||||
int sys_errno, va_list va)
|
||||
{
|
||||
assert(log_class > LOG_CLASS_NONE);
|
||||
assert(log_class < ALEN(log_level_map));
|
||||
|
||||
if (!do_syslog)
|
||||
return;
|
||||
|
||||
if (log_class > log_level)
|
||||
return;
|
||||
|
||||
/* Map our log level to syslog's level */
|
||||
int level = -1;
|
||||
switch (log_class) {
|
||||
case LOG_CLASS_ERROR: level = LOG_ERR; break;
|
||||
case LOG_CLASS_WARNING: level = LOG_WARNING; break;
|
||||
case LOG_CLASS_INFO: level = LOG_INFO; break;
|
||||
case LOG_CLASS_DEBUG: level = LOG_DEBUG; break;
|
||||
}
|
||||
int level = log_level_map[log_class].syslog_equivalent;
|
||||
|
||||
assert(level != -1);
|
||||
char msg[4096];
|
||||
int n = vsnprintf(msg, sizeof(msg), fmt, va);
|
||||
assert(n >= 0);
|
||||
|
||||
const char *sys_err = sys_errno != 0 ? strerror(sys_errno) : NULL;
|
||||
if (sys_errno != 0 && (size_t)n < sizeof(msg))
|
||||
snprintf(msg + n, sizeof(msg) - n, ": %s", strerror(sys_errno));
|
||||
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
|
||||
/* Calculate required size of buffer holding the entire log message */
|
||||
int required_len = 0;
|
||||
required_len += strlen(module) + 2; /* "%s: " */
|
||||
required_len += vsnprintf(NULL, 0, fmt, va2); va_end(va2);
|
||||
|
||||
if (sys_errno != 0)
|
||||
required_len += strlen(sys_err) + 2; /* ": %s" */
|
||||
|
||||
/* Format the msg */
|
||||
char *msg = malloc(required_len + 1);
|
||||
int idx = 0;
|
||||
|
||||
idx += snprintf(&msg[idx], required_len + 1 - idx, "%s: ", module);
|
||||
idx += vsnprintf(&msg[idx], required_len + 1 - idx, fmt, va);
|
||||
|
||||
if (sys_errno != 0) {
|
||||
snprintf(
|
||||
&msg[idx], required_len + 1 - idx, ": %s", strerror(sys_errno));
|
||||
}
|
||||
|
||||
syslog(level, "%s", msg);
|
||||
free(msg);
|
||||
syslog(level, "%s: %s", module, msg);
|
||||
}
|
||||
|
||||
void
|
||||
log_msg(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, const char *fmt, ...)
|
||||
log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, 0, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, 0, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
_log(log_class, module, file, lineno, fmt, 0, va);
|
||||
_sys_log(log_class, module, file, lineno, fmt, 0, va2);
|
||||
va_end(va2);
|
||||
}
|
||||
|
||||
void log_errno(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...)
|
||||
void
|
||||
log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, errno, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, errno, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
log_msg_va(log_class, module, file, lineno, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void log_errno_provided(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...)
|
||||
void
|
||||
log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
|
||||
{
|
||||
va_list ap1, ap2;
|
||||
va_start(ap1, fmt);
|
||||
va_copy(ap2, ap1);
|
||||
_log(log_class, module, file, lineno, fmt, _errno, ap1);
|
||||
_sys_log(log_class, module, file, lineno, fmt, _errno, ap2);
|
||||
va_end(ap1);
|
||||
va_end(ap2);
|
||||
log_errno_provided_va(log_class, module, file, lineno, errno, fmt, va);
|
||||
}
|
||||
|
||||
void
|
||||
log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
log_errno_va(log_class, module, file, lineno, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void
|
||||
log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy,
|
||||
const char *fmt, va_list va)
|
||||
{
|
||||
va_list va2;
|
||||
va_copy(va2, va);
|
||||
_log(log_class, module, file, lineno, fmt, errno_copy, va);
|
||||
_sys_log(log_class, module, file, lineno, fmt, errno_copy, va2);
|
||||
va_end(va2);
|
||||
}
|
||||
|
||||
void
|
||||
log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
log_errno_provided_va(log_class, module, file, lineno, errno_copy, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
static size_t
|
||||
map_len(void)
|
||||
{
|
||||
size_t len = ALEN(log_level_map);
|
||||
#ifndef _DEBUG
|
||||
/* Exclude "debug" entry for non-debug builds */
|
||||
len--;
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
int
|
||||
log_level_from_string(const char *str)
|
||||
{
|
||||
if (str[0] == '\0')
|
||||
return -1;
|
||||
|
||||
for (int i = 0, n = map_len(); i < n; i++)
|
||||
if (strcmp(str, log_level_map[i].name) == 0)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *
|
||||
log_level_string_hint(void)
|
||||
{
|
||||
static char buf[64];
|
||||
if (buf[0] != '\0')
|
||||
return buf;
|
||||
|
||||
for (size_t i = 0, pos = 0, n = map_len(); i < n; i++) {
|
||||
const char *entry = log_level_map[i].name;
|
||||
const char *delim = (i + 1 < n) ? ", " : "";
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "'%s'%s", entry, delim);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
|
55
log.h
55
log.h
|
@ -1,42 +1,43 @@
|
|||
#pragma once
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO };
|
||||
enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON };
|
||||
enum log_class { LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG };
|
||||
|
||||
void log_init(enum log_colorize colorize, bool do_syslog,
|
||||
enum log_facility syslog_facility, enum log_class syslog_level);
|
||||
enum log_class { LOG_CLASS_NONE, LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG };
|
||||
|
||||
void log_init(enum log_colorize colorize, bool do_syslog, enum log_facility syslog_facility, enum log_class log_level);
|
||||
void log_deinit(void);
|
||||
|
||||
void log_msg(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 5, 6)));
|
||||
void log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
__attribute__((format(printf, 5, 6)));
|
||||
|
||||
void log_errno(enum log_class log_class, const char *module,
|
||||
const char *file, int lineno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 5, 6)));
|
||||
void log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
|
||||
__attribute__((format(printf, 5, 6)));
|
||||
|
||||
void log_errno_provided(
|
||||
enum log_class log_class, const char *module,
|
||||
const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...) __attribute__((format (printf, 6, 7)));
|
||||
void log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int _errno,
|
||||
const char *fmt, ...) __attribute__((format(printf, 6, 7)));
|
||||
|
||||
#define LOG_ERR(fmt, ...) \
|
||||
log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_ERRNO(fmt, ...) \
|
||||
log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_ERRNO_P(fmt, _errno, ...) \
|
||||
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, \
|
||||
_errno, fmt, ## __VA_ARGS__)
|
||||
#define LOG_WARN(fmt, ...) \
|
||||
log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_INFO(fmt, ...) \
|
||||
log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
void log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
|
||||
__attribute__((format(printf, 5, 0)));
|
||||
void log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt,
|
||||
va_list va) __attribute__((format(printf, 5, 0)));
|
||||
void log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int _errno,
|
||||
const char *fmt, va_list va) __attribute__((format(printf, 6, 0)));
|
||||
|
||||
int log_level_from_string(const char *str);
|
||||
const char *log_level_string_hint(void);
|
||||
|
||||
#define LOG_ERR(...) log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LOG_ERRNO(...) log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LOG_ERRNO_P(_errno, ...) \
|
||||
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, _errno, __VA_ARGS__)
|
||||
#define LOG_WARN(...) log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LOG_INFO(...) log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
||||
#define LOG_DBG(fmt, ...) \
|
||||
log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
|
||||
#define LOG_DBG(...) log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_DBG(fmt, ...)
|
||||
#define LOG_DBG(...)
|
||||
#endif
|
||||
|
|
85
main.c
85
main.c
|
@ -1,4 +1,6 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
|
@ -9,14 +11,12 @@
|
|||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "bar/bar.h"
|
||||
#include "config.h"
|
||||
|
@ -87,7 +87,7 @@ get_config_path(void)
|
|||
static struct bar *
|
||||
load_bar(const char *config_path, enum bar_backend backend)
|
||||
{
|
||||
FILE *conf_file = fopen(config_path, "r");
|
||||
FILE *conf_file = fopen(config_path, "re");
|
||||
if (conf_file == NULL) {
|
||||
LOG_ERRNO("%s: failed to open", config_path);
|
||||
return NULL;
|
||||
|
@ -127,13 +127,14 @@ print_usage(const char *prog_name)
|
|||
printf("Usage: %s [OPTION]...\n", prog_name);
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -b,--backend={xcb,wayland,auto} backend to use (default: auto)\n"
|
||||
" -c,--config=FILE alternative configuration file\n"
|
||||
" -C,--validate verify configuration then quit\n"
|
||||
" -p,--print-pid=FILE|FD print PID to file or FD\n"
|
||||
" -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
|
||||
" -s,--log-no-syslog disable syslog logging\n"
|
||||
" -v,--version show the version number and quit\n");
|
||||
printf(" -b,--backend={xcb,wayland,auto} backend to use (default: auto)\n"
|
||||
" -c,--config=FILE alternative configuration file\n"
|
||||
" -C,--validate verify configuration then quit\n"
|
||||
" -p,--print-pid=FILE|FD print PID to file or FD\n"
|
||||
" -d,--log-level={info|warning|error|none} log level (warning)\n"
|
||||
" -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
|
||||
" -s,--log-no-syslog disable syslog logging\n"
|
||||
" -v,--version show the version number and quit\n");
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -146,9 +147,8 @@ print_pid(const char *pid_file, bool *unlink_at_exit)
|
|||
int pid_fd = strtoul(pid_file, &end, 10);
|
||||
|
||||
if (errno != 0 || *end != '\0') {
|
||||
if ((pid_fd = open(pid_file,
|
||||
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
|
||||
if ((pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))
|
||||
< 0) {
|
||||
LOG_ERRNO("%s: failed to open", pid_file);
|
||||
return false;
|
||||
} else
|
||||
|
@ -177,15 +177,16 @@ int
|
|||
main(int argc, char *const *argv)
|
||||
{
|
||||
static const struct option longopts[] = {
|
||||
{"backend", required_argument, 0, 'b'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
{"validate", no_argument, 0, 'C'},
|
||||
{"print-pid", required_argument, 0, 'p'},
|
||||
{"log-colorize", optional_argument, 0, 'l'},
|
||||
{"log-no-syslog", no_argument, 0, 's'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{NULL, no_argument, 0, 0},
|
||||
{"backend", required_argument, 0, 'b'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
{"validate", no_argument, 0, 'C'},
|
||||
{"print-pid", required_argument, 0, 'p'},
|
||||
{"log-level", required_argument, 0, 'd'},
|
||||
{"log-colorize", optional_argument, 0, 'l'},
|
||||
{"log-no-syslog", no_argument, 0, 's'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{NULL, no_argument, 0, 0},
|
||||
};
|
||||
|
||||
bool unlink_pid_file = false;
|
||||
|
@ -195,11 +196,12 @@ main(int argc, char *const *argv)
|
|||
char *config_path = NULL;
|
||||
enum bar_backend backend = BAR_BACKEND_AUTO;
|
||||
|
||||
enum log_class log_level = LOG_CLASS_WARNING;
|
||||
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
|
||||
bool log_syslog = true;
|
||||
|
||||
while (true) {
|
||||
int c = getopt_long(argc, argv, ":b:c:Cp:l::svh", longopts, NULL);
|
||||
int c = getopt_long(argc, argv, ":b:c:Cp:d:l::svh", longopts, NULL);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
|
@ -220,9 +222,8 @@ main(int argc, char *const *argv)
|
|||
if (stat(optarg, &st) == -1) {
|
||||
fprintf(stderr, "%s: invalid configuration file: %s\n", optarg, strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
} else if (!S_ISREG(st.st_mode)) {
|
||||
fprintf(stderr, "%s: invalid configuration file: not a regular file\n",
|
||||
optarg);
|
||||
} else if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) {
|
||||
fprintf(stderr, "%s: invalid configuration file: neither a regular file nor a pipe or FIFO\n", optarg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -238,6 +239,16 @@ main(int argc, char *const *argv)
|
|||
pid_file = optarg;
|
||||
break;
|
||||
|
||||
case 'd': {
|
||||
int lvl = log_level_from_string(optarg);
|
||||
if (lvl < 0) {
|
||||
fprintf(stderr, "-d,--log-level: %s: argument must be one of %s\n", optarg, log_level_string_hint());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
log_level = lvl;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'l':
|
||||
if (optarg == NULL || strcmp(optarg, "auto") == 0)
|
||||
log_colorize = LOG_COLORIZE_AUTO;
|
||||
|
@ -273,14 +284,12 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
}
|
||||
|
||||
log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, LOG_CLASS_INFO);
|
||||
log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, log_level);
|
||||
|
||||
_Static_assert(LOG_CLASS_ERROR + 1 == FCFT_LOG_CLASS_ERROR,
|
||||
"fcft log level enum offset");
|
||||
_Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS,
|
||||
"fcft colorize enum mismatch");
|
||||
fcft_log_init(
|
||||
(enum fcft_log_colorize)log_colorize, log_syslog, FCFT_LOG_CLASS_INFO);
|
||||
_Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR, "fcft log level enum offset");
|
||||
_Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch");
|
||||
fcft_init((enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level);
|
||||
atexit(&fcft_fini);
|
||||
|
||||
const struct sigaction sa = {.sa_handler = &signal_handler};
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
|
@ -357,7 +366,7 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
|
||||
if (aborted)
|
||||
LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted);
|
||||
LOG_INFO("aborted: %s (%ld)", strsignal(aborted), (long)aborted);
|
||||
|
||||
done:
|
||||
/* Signal abort to other threads */
|
||||
|
@ -367,7 +376,7 @@ done:
|
|||
int res;
|
||||
int r = thrd_join(bar_thread, &res);
|
||||
if (r != 0)
|
||||
LOG_ERRNO_P("failed to join bar thread", r);
|
||||
LOG_ERRNO_P(r, "failed to join bar thread");
|
||||
|
||||
bar->destroy(bar);
|
||||
close(abort_fd);
|
||||
|
|
69
meson.build
69
meson.build
|
@ -1,20 +1,28 @@
|
|||
project('yambar', 'c',
|
||||
version: '1.6.2',
|
||||
version: '1.11.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.53.0',
|
||||
meson_version: '>=0.60.0',
|
||||
default_options: ['c_std=c18',
|
||||
'warning_level=1',
|
||||
'werror=true',
|
||||
'b_ndebug=if-release'])
|
||||
|
||||
is_debug_build = get_option('buildtype').startswith('debug')
|
||||
plugs_as_libs = get_option('core-plugins-as-shared-libraries')
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
cc_flags = [
|
||||
'-Werror=all'
|
||||
]
|
||||
|
||||
if cc.has_function('memfd_create',
|
||||
args: ['-D_GNU_SOURCE=200809L'],
|
||||
prefix: '#include <sys/mman.h>')
|
||||
add_project_arguments('-DMEMFD_CREATE', language: 'c')
|
||||
endif
|
||||
|
||||
# Compute the relative path used by compiler invocations.
|
||||
source_root = meson.current_source_dir().split('/')
|
||||
build_root = meson.build_root().split('/')
|
||||
build_root = meson.global_build_root().split('/')
|
||||
relative_dir_parts = []
|
||||
i = 0
|
||||
in_prefix = true
|
||||
|
@ -43,7 +51,9 @@ endif
|
|||
# Common dependencies
|
||||
dl = cc.find_library('dl')
|
||||
m = cc.find_library('m')
|
||||
threads = dependency('threads')
|
||||
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
|
||||
libepoll = dependency('epoll-shim', required: false)
|
||||
libinotify = dependency('libinotify', required: false)
|
||||
pixman = dependency('pixman-1')
|
||||
yaml = dependency('yaml-0.1')
|
||||
|
||||
|
@ -65,9 +75,10 @@ backend_wayland = wayland_client.found() and wayland_cursor.found()
|
|||
|
||||
# "My" dependencies, fallback to subproject
|
||||
tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist')
|
||||
fcft = dependency('fcft', version: ['>=2.4.0', '<3.0.0'], fallback: 'fcft')
|
||||
fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft')
|
||||
|
||||
add_project_arguments(
|
||||
cc_flags +
|
||||
['-D_GNU_SOURCE'] +
|
||||
(is_debug_build ? ['-D_DEBUG'] : []) +
|
||||
(backend_x11 ? ['-DENABLE_X11'] : []) +
|
||||
|
@ -84,30 +95,37 @@ if backend_x11
|
|||
c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [],
|
||||
pic: plugs_as_libs)
|
||||
|
||||
xcb_stuff = declare_dependency(link_with: xcb_stuff_lib)
|
||||
xcb_stuff = declare_dependency(
|
||||
link_with: xcb_stuff_lib,
|
||||
dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr,
|
||||
xcb_render, xcb_errors],
|
||||
)
|
||||
install_headers('xcb.h', subdir: 'yambar')
|
||||
endif
|
||||
|
||||
subdir('completions')
|
||||
subdir('doc')
|
||||
subdir('bar')
|
||||
subdir('decorations')
|
||||
subdir('particles')
|
||||
subdir('modules')
|
||||
subdir('doc')
|
||||
|
||||
env = find_program('env', native: true)
|
||||
generate_version_sh = files('generate-version.sh')
|
||||
version = custom_target(
|
||||
'generate_version',
|
||||
build_always_stale: true,
|
||||
output: 'version.h',
|
||||
command: [generate_version_sh, meson.project_version(), '@SOURCE_ROOT@', '@OUTPUT@'])
|
||||
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
|
||||
|
||||
yambar = executable(
|
||||
'yambar',
|
||||
'char32.c', 'char32.h',
|
||||
'color.h',
|
||||
'config-verify.c', 'config-verify.h',
|
||||
'config.c', 'config.h',
|
||||
'decoration.h',
|
||||
'font-shaping.h',
|
||||
'log.c', 'log.h',
|
||||
'main.c',
|
||||
'module.c', 'module.h',
|
||||
|
@ -116,7 +134,7 @@ yambar = executable(
|
|||
'tag.c', 'tag.h',
|
||||
'yml.c', 'yml.h',
|
||||
version,
|
||||
dependencies: [bar, pixman, yaml, threads, dl, tllist, fcft] +
|
||||
dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] +
|
||||
decorations + particles + modules,
|
||||
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
|
||||
export_dynamic: true,
|
||||
|
@ -152,3 +170,34 @@ summary(
|
|||
},
|
||||
bool_yn: true
|
||||
)
|
||||
|
||||
summary(
|
||||
{
|
||||
'ALSA': plugin_alsa_enabled,
|
||||
'Backlight': plugin_backlight_enabled,
|
||||
'Battery': plugin_battery_enabled,
|
||||
'Clock': plugin_clock_enabled,
|
||||
'CPU monitoring': plugin_cpu_enabled,
|
||||
'Disk I/O monitoring': plugin_disk_io_enabled,
|
||||
'dwl (dwm for Wayland)': plugin_dwl_enabled,
|
||||
'Foreign toplevel (window tracking for Wayland)': plugin_foreign_toplevel_enabled,
|
||||
'Memory monitoring': plugin_mem_enabled,
|
||||
'Music Player Daemon (MPD)': plugin_mpd_enabled,
|
||||
'Media Player Remote Interface Specificaion (MPRIS)': plugin_mpris_enabled,
|
||||
'i3+Sway': plugin_i3_enabled,
|
||||
'Label': plugin_label_enabled,
|
||||
'Network monitoring': plugin_network_enabled,
|
||||
'Pipewire': plugin_pipewire_enabled,
|
||||
'PulseAudio': plugin_pulse_enabled,
|
||||
'Removables monitoring': plugin_removables_enabled,
|
||||
'River': plugin_river_enabled,
|
||||
'Script': plugin_script_enabled,
|
||||
'Sway XKB keyboard': plugin_sway_xkb_enabled,
|
||||
'Niri language': plugin_niri_language_enabled,
|
||||
'Niri workspaces': plugin_niri_workspaces_enabled,
|
||||
'XKB keyboard (for X11)': plugin_xkb_enabled,
|
||||
'XWindow (window tracking for X11)': plugin_xwindow_enabled,
|
||||
},
|
||||
section: 'Optional modules',
|
||||
bool_yn: true
|
||||
)
|
||||
|
|
|
@ -5,3 +5,52 @@ option(
|
|||
option(
|
||||
'core-plugins-as-shared-libraries', type: 'boolean', value: false,
|
||||
description: 'Compiles modules, particles and decorations as shared libraries, which are loaded on-demand')
|
||||
|
||||
option('plugin-alsa', type: 'feature', value: 'auto',
|
||||
description: 'ALSA support')
|
||||
option('plugin-backlight', type: 'feature', value: 'auto',
|
||||
description: 'Backlight support')
|
||||
option('plugin-battery', type: 'feature', value: 'auto',
|
||||
description: 'Battery support')
|
||||
option('plugin-clock', type: 'feature', value: 'auto',
|
||||
description: 'Clock support')
|
||||
option('plugin-cpu', type: 'feature', value: 'auto',
|
||||
description: 'CPU monitoring support')
|
||||
option('plugin-disk-io', type: 'feature', value: 'auto',
|
||||
description: 'Disk I/O support')
|
||||
option('plugin-dwl', type: 'feature', value: 'auto',
|
||||
description: 'dwl (dwm for wayland) support')
|
||||
option('plugin-foreign-toplevel', type: 'feature', value: 'auto',
|
||||
description: 'Foreign toplevel (window tracking for Wayland) support')
|
||||
option('plugin-mem', type: 'feature', value: 'auto',
|
||||
description: 'Memory monitoring support')
|
||||
option('plugin-mpd', type: 'feature', value: 'auto',
|
||||
description: 'Music Player Daemon (MPD) support')
|
||||
option('plugin-mpris', type: 'feature', value: 'enabled',
|
||||
description: 'Media Player Remote Interface Specificaion (MPRIS) support')
|
||||
option('plugin-i3', type: 'feature', value: 'auto',
|
||||
description: 'i3+Sway support')
|
||||
option('plugin-label', type: 'feature', value: 'auto',
|
||||
description: 'Label support')
|
||||
option('plugin-network', type: 'feature', value: 'auto',
|
||||
description: 'Network monitoring support')
|
||||
option('plugin-pipewire', type: 'feature', value: 'auto',
|
||||
description: 'Pipewire support')
|
||||
option('plugin-pulse', type: 'feature', value: 'auto',
|
||||
description: 'PulseAudio support')
|
||||
option('plugin-removables', type: 'feature', value: 'auto',
|
||||
description: 'Removables (USB sticks, CD-ROM etc) monitoring support')
|
||||
option('plugin-river', type: 'feature', value: 'auto',
|
||||
description: 'River support')
|
||||
option('plugin-script', type: 'feature', value: 'auto',
|
||||
description: 'Script support')
|
||||
option('plugin-sway-xkb', type: 'feature', value: 'auto',
|
||||
description: 'keyboard support for Sway')
|
||||
option('plugin-niri-language', type: 'feature', value: 'auto',
|
||||
description: 'language support for Niri')
|
||||
option('plugin-niri-workspaces', type: 'feature', value: 'auto',
|
||||
description: 'workspaces support for Niri')
|
||||
option('plugin-xkb', type: 'feature', value: 'auto',
|
||||
description: 'keyboard support for X11')
|
||||
option('plugin-xwindow', type: 'feature', value: 'auto',
|
||||
description: 'XWindow (window tracking for X11) support')
|
||||
|
|
2
module.c
2
module.c
|
@ -1,6 +1,6 @@
|
|||
#include "module.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct module *
|
||||
|
|
14
module.h
14
module.h
|
@ -27,7 +27,7 @@ struct module {
|
|||
* specified number of milliseconds */
|
||||
bool (*refresh_in)(struct module *mod, long milli_seconds);
|
||||
|
||||
const char *(*description)(struct module *mod);
|
||||
const char *(*description)(const struct module *mod);
|
||||
};
|
||||
|
||||
struct module *module_common_new(void);
|
||||
|
@ -35,9 +35,9 @@ void module_default_destroy(struct module *mod);
|
|||
struct exposable *module_begin_expose(struct module *mod);
|
||||
|
||||
/* List of attributes *all* modules implement */
|
||||
#define MODULE_COMMON_ATTRS \
|
||||
{"content", true, &conf_verify_particle}, \
|
||||
{"anchors", false, NULL}, \
|
||||
{"font", false, &conf_verify_font}, \
|
||||
{"foreground", false, &conf_verify_color}, \
|
||||
{NULL, false, NULL}
|
||||
#define MODULE_COMMON_ATTRS \
|
||||
{"content", true, &conf_verify_particle}, {"anchors", false, NULL}, {"font", false, &conf_verify_font}, \
|
||||
{"foreground", false, &conf_verify_color}, \
|
||||
{ \
|
||||
NULL, false, NULL \
|
||||
}
|
||||
|
|
620
modules/alsa.c
620
modules/alsa.c
|
@ -1,6 +1,8 @@
|
|||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
|
@ -8,42 +10,86 @@
|
|||
|
||||
#define LOG_MODULE "alsa"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
char *card;
|
||||
char *mixer;
|
||||
struct particle *label;
|
||||
enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE };
|
||||
|
||||
tll(snd_mixer_selem_channel_id_t) channels;
|
||||
struct channel {
|
||||
snd_mixer_selem_channel_id_t id;
|
||||
enum channel_type type;
|
||||
char *name;
|
||||
|
||||
long vol_min;
|
||||
long vol_max;
|
||||
bool use_db;
|
||||
long vol_cur;
|
||||
long db_cur;
|
||||
bool muted;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
char *card;
|
||||
char *mixer;
|
||||
char *volume_name;
|
||||
char *muted_name;
|
||||
struct particle *label;
|
||||
|
||||
tll(struct channel) channels;
|
||||
|
||||
bool online;
|
||||
|
||||
bool has_playback_volume;
|
||||
long playback_vol_min;
|
||||
long playback_vol_max;
|
||||
|
||||
bool has_playback_db;
|
||||
long playback_db_min;
|
||||
long playback_db_max;
|
||||
|
||||
bool has_capture_volume;
|
||||
long capture_vol_min;
|
||||
long capture_vol_max;
|
||||
|
||||
long has_capture_db;
|
||||
long capture_db_min;
|
||||
long capture_db_max;
|
||||
|
||||
const struct channel *volume_chan;
|
||||
const struct channel *muted_chan;
|
||||
};
|
||||
|
||||
static void
|
||||
channel_free(struct channel *chan)
|
||||
{
|
||||
free(chan->name);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
tll_free(m->channels);
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
channel_free(&it->item);
|
||||
tll_remove(m->channels, it);
|
||||
}
|
||||
m->label->destroy(m->label);
|
||||
free(m->card);
|
||||
free(m->mixer);
|
||||
free(m->volume_name);
|
||||
free(m->muted_name);
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
static char desc[32];
|
||||
struct private *m = mod->private;
|
||||
const struct private *m = mod->private;
|
||||
snprintf(desc, sizeof(desc), "alsa(%s)", m->card);
|
||||
return desc;
|
||||
}
|
||||
|
@ -53,18 +99,60 @@ content(struct module *mod)
|
|||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
int percent = m->vol_max - m->vol_min > 0
|
||||
? round(100. * m->vol_cur / (m->vol_max - m->vol_min))
|
||||
: 0;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const struct channel *volume_chan = m->volume_chan;
|
||||
const struct channel *muted_chan = m->muted_chan;
|
||||
|
||||
bool muted = muted_chan != NULL ? muted_chan->muted : false;
|
||||
long vol_min = 0, vol_max = 0, vol_cur = 0;
|
||||
long db_min = 0, db_max = 0, db_cur = 0;
|
||||
bool use_db = false;
|
||||
|
||||
if (volume_chan != NULL) {
|
||||
if (volume_chan->type == CHANNEL_PLAYBACK) {
|
||||
db_min = m->playback_db_min;
|
||||
db_max = m->playback_db_max;
|
||||
vol_min = m->playback_vol_min;
|
||||
vol_max = m->playback_vol_max;
|
||||
} else {
|
||||
db_min = m->capture_db_min;
|
||||
db_max = m->capture_db_max;
|
||||
vol_min = m->capture_vol_min;
|
||||
vol_max = m->capture_vol_max;
|
||||
}
|
||||
vol_cur = volume_chan->vol_cur;
|
||||
db_cur = volume_chan->db_cur;
|
||||
use_db = volume_chan->use_db;
|
||||
}
|
||||
|
||||
int percent;
|
||||
|
||||
if (use_db) {
|
||||
bool use_linear = db_max - db_min <= 24 * 100;
|
||||
if (use_linear) {
|
||||
percent = db_min - db_max > 0 ? round(100. * (db_cur - db_min) / (db_max - db_min)) : 0;
|
||||
} else {
|
||||
double normalized = pow(10, (double)(db_cur - db_max) / 6000.);
|
||||
if (db_min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
double min_norm = pow(10, (double)(db_min - db_max) / 6000.);
|
||||
normalized = (normalized - min_norm) / (1. - min_norm);
|
||||
}
|
||||
percent = round(100. * normalized);
|
||||
}
|
||||
} else {
|
||||
percent = vol_max - vol_min > 0 ? round(100. * (vol_cur - vol_min) / (vol_max - vol_min)) : 0;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_int_range(mod, "volume", m->vol_cur, m->vol_min, m->vol_max),
|
||||
tag_new_bool(mod, "online", m->online),
|
||||
tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max),
|
||||
tag_new_int_range(mod, "dB", db_cur, db_min, db_max),
|
||||
tag_new_int_range(mod, "percent", percent, 0, 100),
|
||||
tag_new_bool(mod, "muted", m->muted),
|
||||
tag_new_bool(mod, "muted", muted),
|
||||
},
|
||||
.count = 3,
|
||||
.count = 5,
|
||||
};
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
|
@ -79,136 +167,137 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
|
|||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
/* Get min/max volume levels */
|
||||
long min = 0, max = 0;
|
||||
int r = snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_DBG("%s,%s: failed to get volume min/max (mixer is digital?)",
|
||||
m->card, m->mixer);
|
||||
}
|
||||
|
||||
/* Make sure min <= max */
|
||||
if (min > max) {
|
||||
LOG_WARN(
|
||||
"%s,%s: indicated minimum volume is greater than the maximum: "
|
||||
"%ld > %ld", m->card, m->mixer, min, max);
|
||||
min = max;
|
||||
}
|
||||
|
||||
long cur[tll_length(m->channels)];
|
||||
memset(cur, 0, sizeof(cur));
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
/* If volume level can be changed (i.e. this isn't just a switch;
|
||||
* e.g. a digital channel), get current level */
|
||||
if (max > 0) {
|
||||
tll_foreach(m->channels, it) {
|
||||
int r = snd_mixer_selem_get_playback_volume(
|
||||
elem, it->item, &cur[idx]);
|
||||
* e.g. a digital channel), get current channel levels */
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
struct channel *chan = &it->item;
|
||||
|
||||
const bool has_volume = chan->type == CHANNEL_PLAYBACK ? m->has_playback_volume : m->has_capture_volume;
|
||||
const bool has_db = chan->type == CHANNEL_PLAYBACK ? m->has_playback_db : m->has_capture_db;
|
||||
|
||||
if (!has_volume && !has_db)
|
||||
continue;
|
||||
|
||||
if (has_db) {
|
||||
chan->use_db = true;
|
||||
|
||||
const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_db_min : m->capture_db_min;
|
||||
const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_db_max : m->capture_db_max;
|
||||
assert(min <= max);
|
||||
|
||||
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_dB(elem, chan->id, &chan->db_cur)
|
||||
: snd_mixer_selem_get_capture_dB(elem, chan->id, &chan->db_cur);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_WARN("%s,%s: %s: failed to get current volume",
|
||||
m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(it->item));
|
||||
LOG_ERR("%s,%s: %s: failed to get current dB", m->card, m->mixer, chan->name);
|
||||
}
|
||||
|
||||
LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(it->item), cur[idx]);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
if (chan->db_cur < min) {
|
||||
LOG_WARN("%s,%s: %s: current dB is less than the indicated minimum: "
|
||||
"%ld < %ld",
|
||||
m->card, m->mixer, chan->name, chan->db_cur, min);
|
||||
chan->db_cur = min;
|
||||
}
|
||||
|
||||
int unmuted[tll_length(m->channels)];
|
||||
memset(unmuted, 0, sizeof(unmuted));
|
||||
if (chan->db_cur > max) {
|
||||
LOG_WARN("%s,%s: %s: current dB is greater than the indicated maximum: "
|
||||
"%ld > %ld",
|
||||
m->card, m->mixer, chan->name, chan->db_cur, max);
|
||||
chan->db_cur = max;
|
||||
}
|
||||
|
||||
/* Get muted state */
|
||||
idx = 0;
|
||||
tll_foreach(m->channels, it) {
|
||||
int r = snd_mixer_selem_get_playback_switch(
|
||||
elem, it->item, &unmuted[idx]);
|
||||
assert(chan->db_cur >= min);
|
||||
assert(chan->db_cur <= max);
|
||||
|
||||
LOG_DBG("%s,%s: %s: dB: %ld", m->card, m->mixer, chan->name, chan->db_cur);
|
||||
} else
|
||||
chan->use_db = false;
|
||||
|
||||
const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_min : m->capture_vol_min;
|
||||
const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_max : m->capture_vol_max;
|
||||
assert(min <= max);
|
||||
|
||||
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_volume(elem, chan->id, &chan->vol_cur)
|
||||
: snd_mixer_selem_get_capture_volume(elem, chan->id, &chan->vol_cur);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_WARN("%s,%s: %s: failed to get muted state",
|
||||
m->card, m->mixer, snd_mixer_selem_channel_name(it->item));
|
||||
unmuted[idx] = 1;
|
||||
LOG_ERR("%s,%s: %s: failed to get current volume", m->card, m->mixer, chan->name);
|
||||
}
|
||||
|
||||
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(it->item), !unmuted[idx]);
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
/* Warn if volume level is inconsistent across the channels */
|
||||
for (size_t i = 1; i < tll_length(m->channels); i++) {
|
||||
if (cur[i] != cur[i - 1]) {
|
||||
LOG_WARN("%s,%s: channel volume mismatch, using value from %s",
|
||||
m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(tll_front(m->channels)));
|
||||
break;
|
||||
if (chan->vol_cur < min) {
|
||||
LOG_WARN("%s,%s: %s: current volume is less than the indicated minimum: "
|
||||
"%ld < %ld",
|
||||
m->card, m->mixer, chan->name, chan->vol_cur, min);
|
||||
chan->vol_cur = min;
|
||||
}
|
||||
}
|
||||
|
||||
/* Warn if muted state is inconsistent across the channels */
|
||||
for (size_t i = 1; i < tll_length(m->channels); i++) {
|
||||
if (unmuted[i] != unmuted[i - 1]) {
|
||||
LOG_WARN("%s,%s: channel muted mismatch, using value from %s",
|
||||
m->card, m->mixer,
|
||||
snd_mixer_selem_channel_name(tll_front(m->channels)));
|
||||
break;
|
||||
if (chan->vol_cur > max) {
|
||||
LOG_WARN("%s,%s: %s: current volume is greater than the indicated maximum: "
|
||||
"%ld > %ld",
|
||||
m->card, m->mixer, chan->name, chan->vol_cur, max);
|
||||
chan->vol_cur = max;
|
||||
}
|
||||
|
||||
assert(chan->vol_cur >= min);
|
||||
assert(chan->vol_cur <= max);
|
||||
|
||||
LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer, chan->name, chan->vol_cur);
|
||||
}
|
||||
|
||||
/* Make sure min <= cur <= max */
|
||||
if (cur[0] < min) {
|
||||
LOG_WARN(
|
||||
"%s,%s: current volume is less than the indicated minimum: "
|
||||
"%ld < %ld", m->card, m->mixer, cur[0], min);
|
||||
cur[0] = min;
|
||||
/* Get channels’ muted state */
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
struct channel *chan = &it->item;
|
||||
|
||||
int unmuted;
|
||||
|
||||
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_switch(elem, chan->id, &unmuted)
|
||||
: snd_mixer_selem_get_capture_switch(elem, chan->id, &unmuted);
|
||||
|
||||
if (r < 0) {
|
||||
LOG_WARN("%s,%s: %s: failed to get muted state", m->card, m->mixer, chan->name);
|
||||
unmuted = 1;
|
||||
}
|
||||
|
||||
chan->muted = !unmuted;
|
||||
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted);
|
||||
}
|
||||
|
||||
if (cur[0] > max) {
|
||||
LOG_WARN(
|
||||
"%s,%s: current volume is greater than the indicated maximum: "
|
||||
"%ld > %ld", m->card, m->mixer, cur[0], max);
|
||||
cur[0] = max;
|
||||
}
|
||||
m->online = true;
|
||||
|
||||
assert(cur[0] >= min);
|
||||
assert(cur[0] <= max);
|
||||
|
||||
LOG_DBG(
|
||||
"muted=%d, cur=%ld, min=%ld, max=%ld", !unmuted[0], cur[0], min, max);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
m->vol_min = min;
|
||||
m->vol_max = max;
|
||||
m->vol_cur = cur[0];
|
||||
m->muted = !unmuted[0];
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
mod->bar->refresh(mod->bar);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
enum run_state {
|
||||
RUN_ERROR,
|
||||
RUN_FAILED_CONNECT,
|
||||
RUN_DISCONNECTED,
|
||||
RUN_DONE,
|
||||
};
|
||||
|
||||
static enum run_state
|
||||
run_while_online(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
int ret = 1;
|
||||
enum run_state ret = RUN_ERROR;
|
||||
|
||||
/* Make sure we aren’t still tracking channels from previous connects */
|
||||
tll_free(m->channels);
|
||||
|
||||
snd_mixer_t *handle;
|
||||
if (snd_mixer_open(&handle, 0) != 0) {
|
||||
LOG_ERR("failed to open handle");
|
||||
return 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (snd_mixer_attach(handle, m->card) != 0 ||
|
||||
snd_mixer_selem_register(handle, NULL, NULL) != 0 ||
|
||||
snd_mixer_load(handle) != 0)
|
||||
{
|
||||
if (snd_mixer_attach(handle, m->card) != 0 || snd_mixer_selem_register(handle, NULL, NULL) != 0
|
||||
|| snd_mixer_load(handle) != 0) {
|
||||
LOG_ERR("failed to attach to card");
|
||||
ret = RUN_FAILED_CONNECT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
@ -217,37 +306,142 @@ run(struct module *mod)
|
|||
snd_mixer_selem_id_set_index(sid, 0);
|
||||
snd_mixer_selem_id_set_name(sid, m->mixer);
|
||||
|
||||
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
|
||||
snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid);
|
||||
if (elem == NULL) {
|
||||
LOG_ERR("failed to find mixer");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Get playback volume range */
|
||||
m->has_playback_volume = snd_mixer_selem_has_playback_volume(elem) > 0;
|
||||
if (m->has_playback_volume) {
|
||||
if (snd_mixer_selem_get_playback_volume_range(elem, &m->playback_vol_min, &m->playback_vol_max) < 0) {
|
||||
LOG_ERR("%s,%s: failed to get playback volume range", m->card, m->mixer);
|
||||
assert(m->playback_vol_min == 0);
|
||||
assert(m->playback_vol_max == 0);
|
||||
}
|
||||
|
||||
if (m->playback_vol_min > m->playback_vol_max) {
|
||||
LOG_WARN("%s,%s: indicated minimum playback volume is greater than the "
|
||||
"maximum: %ld > %ld",
|
||||
m->card, m->mixer, m->playback_vol_min, m->playback_vol_max);
|
||||
m->playback_vol_min = m->playback_vol_max;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_mixer_selem_get_playback_dB_range(elem, &m->playback_db_min, &m->playback_db_max) < 0) {
|
||||
LOG_WARN("%s,%s: failed to get playback dB range, "
|
||||
"will use raw volume values instead",
|
||||
m->card, m->mixer);
|
||||
m->has_playback_db = false;
|
||||
} else
|
||||
m->has_playback_db = true;
|
||||
|
||||
/* Get capture volume range */
|
||||
m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0;
|
||||
if (m->has_capture_volume) {
|
||||
if (snd_mixer_selem_get_capture_volume_range(elem, &m->capture_vol_min, &m->capture_vol_max) < 0) {
|
||||
LOG_ERR("%s,%s: failed to get capture volume range", m->card, m->mixer);
|
||||
assert(m->capture_vol_min == 0);
|
||||
assert(m->capture_vol_max == 0);
|
||||
}
|
||||
|
||||
if (m->capture_vol_min > m->capture_vol_max) {
|
||||
LOG_WARN("%s,%s: indicated minimum capture volume is greater than the "
|
||||
"maximum: %ld > %ld",
|
||||
m->card, m->mixer, m->capture_vol_min, m->capture_vol_max);
|
||||
m->capture_vol_min = m->capture_vol_max;
|
||||
}
|
||||
}
|
||||
|
||||
if (snd_mixer_selem_get_capture_dB_range(elem, &m->capture_db_min, &m->capture_db_max) < 0) {
|
||||
LOG_WARN("%s,%s: failed to get capture dB range, "
|
||||
"will use raw volume values instead",
|
||||
m->card, m->mixer);
|
||||
m->has_capture_db = false;
|
||||
} else
|
||||
m->has_capture_db = true;
|
||||
|
||||
/* Get available channels */
|
||||
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) {
|
||||
if (snd_mixer_selem_has_playback_channel(elem, i)) {
|
||||
tll_push_back(m->channels, i);
|
||||
bool is_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1;
|
||||
bool is_capture = snd_mixer_selem_has_capture_channel(elem, i) == 1;
|
||||
|
||||
if (is_playback || is_capture) {
|
||||
struct channel chan = {
|
||||
.id = i,
|
||||
.type = is_playback ? CHANNEL_PLAYBACK : CHANNEL_CAPTURE,
|
||||
.name = strdup(snd_mixer_selem_channel_name(i)),
|
||||
};
|
||||
tll_push_back(m->channels, chan);
|
||||
}
|
||||
}
|
||||
|
||||
if (tll_length(m->channels) == 0) {
|
||||
LOG_ERR("%s,%s: no channels", m->card, m->mixer);
|
||||
goto err;
|
||||
}
|
||||
|
||||
char channels_str[1024];
|
||||
int channels_idx = 0;
|
||||
tll_foreach(m->channels, it) {
|
||||
channels_idx += snprintf(
|
||||
&channels_str[channels_idx], sizeof(channels_str) - channels_idx,
|
||||
channels_idx == 0 ? "%s" : ", %s",
|
||||
snd_mixer_selem_channel_name(it->item));
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
const struct channel *chan = &it->item;
|
||||
|
||||
channels_idx += snprintf(&channels_str[channels_idx], sizeof(channels_str) - channels_idx,
|
||||
channels_idx == 0 ? "%s (%s)" : ", %s (%s)", chan->name,
|
||||
chan->type == CHANNEL_PLAYBACK ? "🔊" : "🎤");
|
||||
assert(channels_idx <= sizeof(channels_str));
|
||||
}
|
||||
|
||||
LOG_INFO("%s,%s: channels: %s", m->card, m->mixer, channels_str);
|
||||
|
||||
/* Verify volume/muted channel names are valid and exists */
|
||||
bool volume_channel_is_valid = m->volume_name == NULL;
|
||||
bool muted_channel_is_valid = m->muted_name == NULL;
|
||||
|
||||
tll_foreach(m->channels, it)
|
||||
{
|
||||
const struct channel *chan = &it->item;
|
||||
if (m->volume_name != NULL && strcmp(chan->name, m->volume_name) == 0) {
|
||||
m->volume_chan = chan;
|
||||
volume_channel_is_valid = true;
|
||||
}
|
||||
if (m->muted_name != NULL && strcmp(chan->name, m->muted_name) == 0) {
|
||||
m->muted_chan = chan;
|
||||
muted_channel_is_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m->volume_name == NULL)
|
||||
m->volume_chan = &tll_front(m->channels);
|
||||
if (m->muted_name == NULL)
|
||||
m->muted_chan = &tll_front(m->channels);
|
||||
|
||||
if (!volume_channel_is_valid) {
|
||||
assert(m->volume_name != NULL);
|
||||
LOG_ERR("volume: invalid channel name: %s", m->volume_name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!muted_channel_is_valid) {
|
||||
assert(m->muted_name != NULL);
|
||||
LOG_ERR("muted: invalid channel name: %s", m->muted_name);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Initial state */
|
||||
update_state(mod, elem);
|
||||
|
||||
LOG_INFO("%s,%s: volume min=%ld, max=%ld, current=%ld%s",
|
||||
m->card, m->mixer, m->vol_min, m->vol_max, m->vol_cur,
|
||||
m->muted ? ", muted" : "");
|
||||
LOG_INFO(
|
||||
"%s,%s: %s range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)", m->card, m->mixer,
|
||||
m->volume_chan->use_db ? "dB" : "volume",
|
||||
(m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_min : m->playback_vol_min)
|
||||
: (m->volume_chan->use_db ? m->capture_db_min : m->capture_vol_min)),
|
||||
(m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_max : m->playback_vol_max)
|
||||
: (m->volume_chan->use_db ? m->capture_db_max : m->capture_vol_max)),
|
||||
m->volume_chan->use_db ? m->volume_chan->db_cur : m->volume_chan->vol_cur,
|
||||
m->muted_chan->muted ? " (muted)" : "", m->volume_chan->name, m->muted_chan->name);
|
||||
|
||||
mod->bar->refresh(mod->bar);
|
||||
|
||||
|
@ -260,36 +454,181 @@ run(struct module *mod)
|
|||
fds[0] = (struct pollfd){.fd = mod->abort_fd, .events = POLLIN};
|
||||
snd_mixer_poll_descriptors(handle, &fds[1], fd_count);
|
||||
|
||||
poll(fds, fd_count + 1, -1);
|
||||
int r = poll(fds, fd_count + 1, -1);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
/* Don't know if this can happen */
|
||||
LOG_ERR("disconnected from alsa");
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = RUN_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < fd_count; i++) {
|
||||
if (fds[1 + i].revents & (POLLHUP | POLLERR | POLLNVAL)) {
|
||||
LOG_ERR("disconnected from alsa");
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
m->online = false;
|
||||
mtx_unlock(&mod->lock);
|
||||
mod->bar->refresh(mod->bar);
|
||||
|
||||
ret = RUN_DISCONNECTED;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
snd_mixer_handle_events(handle);
|
||||
update_state(mod, elem);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
snd_mixer_close(handle);
|
||||
snd_config_update_free_global();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
int ret = 1;
|
||||
|
||||
int ifd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
if (ifd < 0) {
|
||||
LOG_ERRNO("failed to inotify");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wd = inotify_add_watch(ifd, "/dev/snd", IN_CREATE);
|
||||
if (wd < 0) {
|
||||
LOG_ERRNO("failed to create inotify watcher for /dev/snd");
|
||||
close(ifd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
enum run_state state = run_while_online(mod);
|
||||
|
||||
switch (state) {
|
||||
case RUN_DONE:
|
||||
ret = 0;
|
||||
goto out;
|
||||
|
||||
case RUN_ERROR:
|
||||
ret = 1;
|
||||
goto out;
|
||||
|
||||
case RUN_FAILED_CONNECT:
|
||||
break;
|
||||
|
||||
case RUN_DISCONNECTED:
|
||||
/*
|
||||
* We’ve been connected - drain the watcher
|
||||
*
|
||||
* We don’t want old, un-releated events (for other
|
||||
* soundcards, for example) to trigger a storm of
|
||||
* re-connect attempts.
|
||||
*/
|
||||
while (true) {
|
||||
uint8_t buf[1024];
|
||||
ssize_t amount = read(ifd, buf, sizeof(buf));
|
||||
if (amount < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
LOG_ERRNO("failed to drain inotify watcher");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (amount == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bool have_create_event = false;
|
||||
|
||||
while (!have_create_event) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = ifd, .events = POLLIN}};
|
||||
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fds[0].revents & (POLLIN | POLLHUP)) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_ERR("inotify socket closed");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
assert(fds[1].revents & POLLIN);
|
||||
|
||||
while (true) {
|
||||
char buf[1024];
|
||||
ssize_t len = read(ifd, buf, sizeof(buf));
|
||||
|
||||
if (len < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
|
||||
LOG_ERRNO("failed to read inotify events");
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (len == 0)
|
||||
break;
|
||||
|
||||
/* Consume inotify data */
|
||||
for (const char *ptr = buf; ptr < buf + len;) {
|
||||
const struct inotify_event *e = (const struct inotify_event *)ptr;
|
||||
|
||||
if (e->mask & IN_CREATE) {
|
||||
LOG_DBG("inotify: CREATED: /dev/snd/%.*s", e->len, e->name);
|
||||
have_create_event = true;
|
||||
}
|
||||
|
||||
ptr += sizeof(*e) + e->len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (wd >= 0)
|
||||
inotify_rm_watch(ifd, wd);
|
||||
if (ifd >= 0)
|
||||
close(ifd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
alsa_new(const char *card, const char *mixer, struct particle *label)
|
||||
alsa_new(const char *card, const char *mixer, const char *volume_channel_name, const char *muted_channel_name,
|
||||
struct particle *label)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->label = label;
|
||||
priv->card = strdup(card);
|
||||
priv->mixer = strdup(mixer);
|
||||
priv->volume_name = volume_channel_name != NULL ? strdup(volume_channel_name) : NULL;
|
||||
priv->muted_name = muted_channel_name != NULL ? strdup(muted_channel_name) : NULL;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = priv;
|
||||
|
@ -305,12 +644,13 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
{
|
||||
const struct yml_node *card = yml_get_value(node, "card");
|
||||
const struct yml_node *mixer = yml_get_value(node, "mixer");
|
||||
const struct yml_node *volume = yml_get_value(node, "volume");
|
||||
const struct yml_node *muted = yml_get_value(node, "muted");
|
||||
const struct yml_node *content = yml_get_value(node, "content");
|
||||
|
||||
return alsa_new(
|
||||
yml_value_as_string(card),
|
||||
yml_value_as_string(mixer),
|
||||
conf_to_particle(content, inherited));
|
||||
return alsa_new(yml_value_as_string(card), yml_value_as_string(mixer),
|
||||
volume != NULL ? yml_value_as_string(volume) : NULL,
|
||||
muted != NULL ? yml_value_as_string(muted) : NULL, conf_to_particle(content, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -319,6 +659,8 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
static const struct attr_info attrs[] = {
|
||||
{"card", true, &conf_verify_string},
|
||||
{"mixer", true, &conf_verify_string},
|
||||
{"volume", false, &conf_verify_string},
|
||||
{"muted", false, &conf_verify_string},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <libudev.h>
|
||||
|
||||
#define LOG_MODULE "backlight"
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
|
||||
char *device;
|
||||
|
@ -39,7 +41,7 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "backlight";
|
||||
}
|
||||
|
@ -110,13 +112,13 @@ readint_from_fd(int fd)
|
|||
static int
|
||||
initialize(struct private *m)
|
||||
{
|
||||
int backlight_fd = open("/sys/class/backlight", O_RDONLY);
|
||||
int backlight_fd = open("/sys/class/backlight", O_RDONLY | O_CLOEXEC);
|
||||
if (backlight_fd == -1) {
|
||||
LOG_ERRNO("/sys/class/backlight");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY);
|
||||
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY | O_CLOEXEC);
|
||||
close(backlight_fd);
|
||||
|
||||
if (base_dir_fd == -1) {
|
||||
|
@ -124,7 +126,7 @@ initialize(struct private *m)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY);
|
||||
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY | O_CLOEXEC);
|
||||
if (max_fd == -1) {
|
||||
LOG_ERRNO("/sys/class/backlight/%s/max_brightness", m->device);
|
||||
close(base_dir_fd);
|
||||
|
@ -134,7 +136,7 @@ initialize(struct private *m)
|
|||
m->max_brightness = readint_from_fd(max_fd);
|
||||
close(max_fd);
|
||||
|
||||
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY);
|
||||
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY | O_CLOEXEC);
|
||||
close(base_dir_fd);
|
||||
|
||||
if (current_fd == -1) {
|
||||
|
@ -144,8 +146,7 @@ initialize(struct private *m)
|
|||
|
||||
m->current_brightness = readint_from_fd(current_fd);
|
||||
|
||||
LOG_INFO("%s: brightness: %ld (max: %ld)", m->device, m->current_brightness,
|
||||
m->max_brightness);
|
||||
LOG_INFO("%s: brightness: %ld (max: %ld)", m->device, m->current_brightness, m->max_brightness);
|
||||
|
||||
return current_fd;
|
||||
}
|
||||
|
@ -178,20 +179,31 @@ run(struct module *mod)
|
|||
|
||||
bar->refresh(bar);
|
||||
|
||||
int ret = 1;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = udev_monitor_get_fd(mon), .events = POLLIN},
|
||||
};
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
struct udev_device *dev = udev_monitor_receive_device(mon);
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
if (dev == NULL)
|
||||
continue;
|
||||
|
||||
bool is_us = strcmp(sysname, m->device) == 0;
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
bool is_us = sysname != NULL && strcmp(sysname, m->device) == 0;
|
||||
udev_device_unref(dev);
|
||||
|
||||
if (!is_us)
|
||||
|
@ -207,7 +219,7 @@ run(struct module *mod)
|
|||
udev_unref(udev);
|
||||
|
||||
close(current_fd);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
|
@ -232,8 +244,7 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *name = yml_get_value(node, "name");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return backlight_new(
|
||||
yml_value_as_string(name), conf_to_particle(c, inherited));
|
||||
return backlight_new(yml_value_as_string(name), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
|
@ -1,31 +1,49 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <libudev.h>
|
||||
#include <tllist.h>
|
||||
|
||||
#define LOG_MODULE "battery"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING };
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
struct private {
|
||||
static const long min_poll_interval = 250;
|
||||
static const long default_poll_interval = 60 * 1000;
|
||||
static const long one_sec_in_ns = 1000000000;
|
||||
|
||||
enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING, STATE_UNKNOWN };
|
||||
|
||||
struct current_state {
|
||||
long ema;
|
||||
long current;
|
||||
struct timespec time;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
|
||||
int poll_interval;
|
||||
long poll_interval;
|
||||
int battery_scale;
|
||||
long smoothing_scale;
|
||||
char *battery;
|
||||
char *manufacturer;
|
||||
char *model;
|
||||
|
@ -39,10 +57,64 @@ struct private {
|
|||
long energy;
|
||||
long power;
|
||||
long charge;
|
||||
long current;
|
||||
struct current_state ema_current;
|
||||
long time_to_empty;
|
||||
long time_to_full;
|
||||
};
|
||||
|
||||
static int64_t
|
||||
difftimespec_ns(const struct timespec after, const struct timespec before)
|
||||
{
|
||||
return ((int64_t)after.tv_sec - (int64_t)before.tv_sec) * (int64_t)one_sec_in_ns
|
||||
+ ((int64_t)after.tv_nsec - (int64_t)before.tv_nsec);
|
||||
}
|
||||
|
||||
// Linear Exponential Moving Average (unevenly spaced time series)
|
||||
// http://www.eckner.com/papers/Algorithms%20for%20Unevenly%20Spaced%20Time%20Series.pdf
|
||||
// Adapted from: https://github.com/andreas50/utsAlgorithms/blob/master/ema.c
|
||||
static void
|
||||
ema_linear(struct current_state *state, struct current_state curr, long tau)
|
||||
{
|
||||
double w, w2, tmp;
|
||||
|
||||
if (state->current == -1) {
|
||||
*state = curr;
|
||||
return;
|
||||
}
|
||||
|
||||
long time = difftimespec_ns(curr.time, state->time);
|
||||
tmp = time / (double)tau;
|
||||
w = exp(-tmp);
|
||||
if (tmp > 1e-6) {
|
||||
w2 = (1 - w) / tmp;
|
||||
} else {
|
||||
// Use taylor expansion for numerical stability
|
||||
w2 = 1 - tmp / 2 + tmp * tmp / 6 - tmp * tmp * tmp / 24;
|
||||
}
|
||||
|
||||
double ema = state->ema * w + curr.current * (1 - w2) + state->current * (w2 - w);
|
||||
|
||||
state->ema = ema;
|
||||
state->current = curr.current;
|
||||
state->time = curr.time;
|
||||
|
||||
LOG_DBG("ema current: %ld", (long)ema);
|
||||
}
|
||||
|
||||
static void
|
||||
timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res)
|
||||
{
|
||||
|
||||
res->tv_sec = a->tv_sec - b->tv_sec;
|
||||
res->tv_nsec = a->tv_nsec - b->tv_nsec;
|
||||
|
||||
/* tv_nsec may be negative */
|
||||
if (res->tv_nsec < 0) {
|
||||
res->tv_sec--;
|
||||
res->tv_nsec += one_sec_in_ns;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
|
@ -58,10 +130,10 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
static char desc[32];
|
||||
struct private *m = mod->private;
|
||||
const struct private *m = mod->private;
|
||||
snprintf(desc, sizeof(desc), "bat(%s)", m->battery);
|
||||
return desc;
|
||||
}
|
||||
|
@ -73,20 +145,22 @@ content(struct module *mod)
|
|||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
assert(m->state == STATE_FULL ||
|
||||
m->state == STATE_NOTCHARGING ||
|
||||
m->state == STATE_CHARGING ||
|
||||
m->state == STATE_DISCHARGING);
|
||||
assert(m->state == STATE_FULL || m->state == STATE_NOTCHARGING || m->state == STATE_CHARGING
|
||||
|| m->state == STATE_DISCHARGING || m->state == STATE_UNKNOWN);
|
||||
|
||||
unsigned long hours;
|
||||
unsigned long minutes;
|
||||
|
||||
if (m->time_to_empty >= 0) {
|
||||
hours = m->time_to_empty / 60;
|
||||
minutes = m->time_to_empty % 60;
|
||||
} else if (m->energy_full >= 0 && m->charge && m->power >= 0) {
|
||||
unsigned long energy = m->state == STATE_CHARGING
|
||||
? m->energy_full - m->energy : m->energy;
|
||||
if (m->time_to_empty > 0) {
|
||||
minutes = m->time_to_empty / 60;
|
||||
hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
} else if (m->time_to_full > 0) {
|
||||
minutes = m->time_to_full / 60;
|
||||
hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
} else if (m->energy_full >= 0 && m->charge && m->power >= 0) {
|
||||
unsigned long energy = m->state == STATE_CHARGING ? m->energy_full - m->energy : m->energy;
|
||||
|
||||
double hours_as_float;
|
||||
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
|
||||
|
@ -98,15 +172,14 @@ content(struct module *mod)
|
|||
|
||||
hours = hours_as_float;
|
||||
minutes = (hours_as_float - (double)hours) * 60;
|
||||
} else if (m->charge_full >= 0 && m->charge >= 0 && m->current >= 0) {
|
||||
unsigned long charge = m->state == STATE_CHARGING
|
||||
? m->charge_full - m->charge : m->charge;
|
||||
} else if (m->charge_full >= 0 && m->charge >= 0 && m->ema_current.current >= 0) {
|
||||
unsigned long charge = m->state == STATE_CHARGING ? m->charge_full - m->charge : m->charge;
|
||||
|
||||
double hours_as_float;
|
||||
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
|
||||
hours_as_float = 0.0;
|
||||
else if (m->current > 0)
|
||||
hours_as_float = (double)charge / m->current;
|
||||
else if (m->ema_current.current > 0)
|
||||
hours_as_float = (double)charge / m->ema_current.current;
|
||||
else
|
||||
hours_as_float = 99.0;
|
||||
|
||||
|
@ -146,20 +219,18 @@ content(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
readline_from_fd(int fd)
|
||||
readline_from_fd(int fd, size_t sz, char buf[static sz])
|
||||
{
|
||||
static char buf[4096];
|
||||
|
||||
ssize_t sz = read(fd, buf, sizeof(buf) - 1);
|
||||
ssize_t bytes = read(fd, buf, sz - 1);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
if (sz < 0) {
|
||||
if (bytes < 0) {
|
||||
LOG_WARN("failed to read from FD=%d", fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf[sz] = '\0';
|
||||
for (ssize_t i = sz - 1; i >= 0 && buf[i] == '\n'; sz--)
|
||||
buf[bytes] = '\0';
|
||||
for (ssize_t i = bytes - 1; i >= 0 && buf[i] == '\n'; bytes--)
|
||||
buf[i] = '\0';
|
||||
|
||||
return buf;
|
||||
|
@ -168,7 +239,8 @@ readline_from_fd(int fd)
|
|||
static long
|
||||
readint_from_fd(int fd)
|
||||
{
|
||||
const char *s = readline_from_fd(fd);
|
||||
char buf[512];
|
||||
const char *s = readline_from_fd(fd, sizeof(buf), buf);
|
||||
if (s == NULL)
|
||||
return 0;
|
||||
|
||||
|
@ -185,13 +257,15 @@ readint_from_fd(int fd)
|
|||
static bool
|
||||
initialize(struct private *m)
|
||||
{
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
|
||||
char line_buf[512];
|
||||
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
|
||||
if (pw_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply");
|
||||
return false;
|
||||
}
|
||||
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
|
||||
close(pw_fd);
|
||||
|
||||
if (base_dir_fd < 0) {
|
||||
|
@ -200,34 +274,31 @@ initialize(struct private *m)
|
|||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_WARN("/sys/class/power_supply/%s/manufacturer: %s",
|
||||
m->battery, strerror(errno));
|
||||
LOG_WARN("/sys/class/power_supply/%s/manufacturer: %s", m->battery, strerror(errno));
|
||||
m->manufacturer = NULL;
|
||||
} else {
|
||||
m->manufacturer = strdup(readline_from_fd(fd));
|
||||
m->manufacturer = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf));
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "model_name", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "model_name", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_WARN("/sys/class/power_supply/%s/model_name: %s",
|
||||
m->battery, strerror(errno));
|
||||
LOG_WARN("/sys/class/power_supply/%s/model_name: %s", m->battery, strerror(errno));
|
||||
m->model = NULL;
|
||||
} else {
|
||||
m->model = strdup(readline_from_fd(fd));
|
||||
m->model = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf));
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (faccessat(base_dir_fd, "energy_full_design", O_RDONLY, 0) == 0 &&
|
||||
faccessat(base_dir_fd, "energy_full", O_RDONLY, 0) == 0)
|
||||
{
|
||||
if (faccessat(base_dir_fd, "energy_full_design", O_RDONLY, 0) == 0
|
||||
&& faccessat(base_dir_fd, "energy_full", O_RDONLY, 0) == 0) {
|
||||
{
|
||||
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery);
|
||||
goto err;
|
||||
|
@ -238,7 +309,7 @@ initialize(struct private *m)
|
|||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "energy_full", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "energy_full", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery);
|
||||
goto err;
|
||||
|
@ -251,28 +322,27 @@ initialize(struct private *m)
|
|||
m->energy_full = m->energy_full_design = -1;
|
||||
}
|
||||
|
||||
if (faccessat(base_dir_fd, "charge_full_design", O_RDONLY, 0) == 0 &&
|
||||
faccessat(base_dir_fd, "charge_full", O_RDONLY, 0) == 0)
|
||||
{
|
||||
if (faccessat(base_dir_fd, "charge_full_design", O_RDONLY, 0) == 0
|
||||
&& faccessat(base_dir_fd, "charge_full", O_RDONLY, 0) == 0) {
|
||||
{
|
||||
int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery);
|
||||
goto err;
|
||||
}
|
||||
|
||||
m->charge_full_design = readint_from_fd(fd);
|
||||
m->charge_full_design = readint_from_fd(fd) / m->battery_scale;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
{
|
||||
int fd = openat(base_dir_fd, "charge_full", O_RDONLY);
|
||||
int fd = openat(base_dir_fd, "charge_full", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery);
|
||||
goto err;
|
||||
}
|
||||
|
||||
m->charge_full = readint_from_fd(fd);
|
||||
m->charge_full = readint_from_fd(fd) / m->battery_scale;
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
|
@ -292,13 +362,13 @@ update_status(struct module *mod)
|
|||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
|
||||
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
|
||||
if (pw_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply");
|
||||
return false;
|
||||
}
|
||||
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
|
||||
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
|
||||
close(pw_fd);
|
||||
|
||||
if (base_dir_fd < 0) {
|
||||
|
@ -306,14 +376,14 @@ update_status(struct module *mod)
|
|||
return false;
|
||||
}
|
||||
|
||||
int status_fd = openat(base_dir_fd, "status", O_RDONLY);
|
||||
int status_fd = openat(base_dir_fd, "status", O_RDONLY | O_CLOEXEC);
|
||||
if (status_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery);
|
||||
close(base_dir_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY);
|
||||
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY | O_CLOEXEC);
|
||||
if (capacity_fd < 0) {
|
||||
LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery);
|
||||
close(status_fd);
|
||||
|
@ -321,11 +391,12 @@ update_status(struct module *mod)
|
|||
return false;
|
||||
}
|
||||
|
||||
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY);
|
||||
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY);
|
||||
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY);
|
||||
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY);
|
||||
int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY);
|
||||
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY | O_CLOEXEC);
|
||||
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY | O_CLOEXEC);
|
||||
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY | O_CLOEXEC);
|
||||
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY | O_CLOEXEC);
|
||||
int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY | O_CLOEXEC);
|
||||
int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY | O_CLOEXEC);
|
||||
|
||||
long capacity = readint_from_fd(capacity_fd);
|
||||
long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1;
|
||||
|
@ -333,8 +404,14 @@ update_status(struct module *mod)
|
|||
long charge = charge_fd >= 0 ? readint_from_fd(charge_fd) : -1;
|
||||
long current = current_fd >= 0 ? readint_from_fd(current_fd) : -1;
|
||||
long time_to_empty = time_to_empty_fd >= 0 ? readint_from_fd(time_to_empty_fd) : -1;
|
||||
long time_to_full = time_to_full_fd >= 0 ? readint_from_fd(time_to_full_fd) : -1;
|
||||
|
||||
const char *status = readline_from_fd(status_fd);
|
||||
if (charge >= -1) {
|
||||
charge /= m->battery_scale;
|
||||
}
|
||||
|
||||
char buf[512];
|
||||
const char *status = readline_from_fd(status_fd, sizeof(buf), buf);
|
||||
|
||||
if (status_fd >= 0)
|
||||
close(status_fd);
|
||||
|
@ -350,6 +427,8 @@ update_status(struct module *mod)
|
|||
close(current_fd);
|
||||
if (time_to_empty_fd >= 0)
|
||||
close(time_to_empty_fd);
|
||||
if (time_to_full_fd >= 0)
|
||||
close(time_to_full_fd);
|
||||
if (base_dir_fd >= 0)
|
||||
close(base_dir_fd);
|
||||
|
||||
|
@ -357,7 +436,7 @@ update_status(struct module *mod)
|
|||
|
||||
if (status == NULL) {
|
||||
LOG_WARN("failed to read battery state");
|
||||
state = STATE_DISCHARGING;
|
||||
state = STATE_UNKNOWN;
|
||||
} else if (strcmp(status, "Full") == 0)
|
||||
state = STATE_FULL;
|
||||
else if (strcmp(status, "Not charging") == 0)
|
||||
|
@ -367,24 +446,32 @@ update_status(struct module *mod)
|
|||
else if (strcmp(status, "Discharging") == 0)
|
||||
state = STATE_DISCHARGING;
|
||||
else if (strcmp(status, "Unknown") == 0)
|
||||
state = STATE_DISCHARGING;
|
||||
state = STATE_UNKNOWN;
|
||||
else {
|
||||
LOG_ERR("unrecognized battery state: %s", status);
|
||||
state = STATE_DISCHARGING;
|
||||
state = STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
LOG_DBG("capacity: %ld, energy: %ld, power: %ld, charge=%ld, current=%ld, "
|
||||
"time-to-empty: %ld", capacity, energy, power, charge, current,
|
||||
time_to_empty);
|
||||
"time-to-empty: %ld, time-to-full: %ld",
|
||||
capacity, energy, power, charge, current, time_to_empty, time_to_full);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
if (m->state != state) {
|
||||
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
|
||||
}
|
||||
m->state = state;
|
||||
m->capacity = capacity;
|
||||
m->energy = energy;
|
||||
m->power = power;
|
||||
m->charge = charge;
|
||||
m->current = current;
|
||||
if (current != -1) {
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
ema_linear(&m->ema_current, (struct current_state){current, current, t}, m->smoothing_scale);
|
||||
}
|
||||
m->time_to_empty = time_to_empty;
|
||||
m->time_to_full = time_to_full;
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
}
|
||||
|
@ -398,13 +485,10 @@ run(struct module *mod)
|
|||
if (!initialize(m))
|
||||
return -1;
|
||||
|
||||
LOG_INFO("%s: %s %s (at %.1f%% of original capacity)",
|
||||
m->battery, m->manufacturer, m->model,
|
||||
(m->energy_full > 0
|
||||
? 100.0 * m->energy_full / m->energy_full_design
|
||||
: m->charge_full > 0
|
||||
? 100.0 * m->charge_full / m->charge_full_design
|
||||
: 0.0));
|
||||
LOG_INFO("%s: %s %s (at %.1f%% of original capacity)", m->battery, m->manufacturer, m->model,
|
||||
(m->energy_full > 0 ? 100.0 * m->energy_full / m->energy_full_design
|
||||
: m->charge_full > 0 ? 100.0 * m->charge_full / m->charge_full_design
|
||||
: 0.0));
|
||||
|
||||
int ret = 1;
|
||||
|
||||
|
@ -422,31 +506,82 @@ run(struct module *mod)
|
|||
|
||||
bar->refresh(bar);
|
||||
|
||||
int timeout_left_ms = m->poll_interval;
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = udev_monitor_get_fd(mon), .events = POLLIN},
|
||||
};
|
||||
poll(fds, 2, m->poll_interval > 0 ? m->poll_interval * 1000 : -1);
|
||||
|
||||
int timeout = m->poll_interval > 0 ? timeout_left_ms : -1;
|
||||
|
||||
struct timespec time_before_poll;
|
||||
if (clock_gettime(CLOCK_BOOTTIME, &time_before_poll) < 0) {
|
||||
LOG_ERRNO("failed to get current time");
|
||||
break;
|
||||
}
|
||||
|
||||
const int poll_ret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout);
|
||||
|
||||
if (poll_ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bool udev_for_us = false;
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
struct udev_device *dev = udev_monitor_receive_device(mon);
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
if (dev != NULL) {
|
||||
const char *sysname = udev_device_get_sysname(dev);
|
||||
udev_for_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
|
||||
|
||||
bool is_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
|
||||
udev_device_unref(dev);
|
||||
if (!udev_for_us) {
|
||||
LOG_DBG("udev notification not for us (%s != %s)", m->battery,
|
||||
sysname != sysname ? sysname : "NULL");
|
||||
} else
|
||||
LOG_DBG("triggering update due to udev notification");
|
||||
|
||||
if (!is_us)
|
||||
continue;
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
}
|
||||
|
||||
if (update_status(mod))
|
||||
bar->refresh(bar);
|
||||
if (udev_for_us || poll_ret == 0) {
|
||||
if (update_status(mod))
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
if (poll_ret == 0 || udev_for_us) {
|
||||
LOG_DBG("resetting timeout-left to %ldms", m->poll_interval);
|
||||
timeout_left_ms = m->poll_interval;
|
||||
} else {
|
||||
struct timespec time_after_poll;
|
||||
if (clock_gettime(CLOCK_BOOTTIME, &time_after_poll) < 0) {
|
||||
LOG_ERRNO("failed to get current time");
|
||||
break;
|
||||
}
|
||||
|
||||
struct timespec timeout_consumed;
|
||||
timespec_sub(&time_after_poll, &time_before_poll, &timeout_consumed);
|
||||
|
||||
const int timeout_consumed_ms = timeout_consumed.tv_sec * 1000 + timeout_consumed.tv_nsec / 1000000;
|
||||
|
||||
LOG_DBG("timeout-left before: %dms, consumed: %dms, updated: %dms", timeout_left_ms, timeout_consumed_ms,
|
||||
max(timeout_left_ms - timeout_consumed_ms, 0));
|
||||
|
||||
timeout_left_ms -= timeout_consumed_ms;
|
||||
if (timeout_left_ms < 0)
|
||||
timeout_left_ms = 0;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -458,13 +593,17 @@ out:
|
|||
}
|
||||
|
||||
static struct module *
|
||||
battery_new(const char *battery, struct particle *label, int poll_interval_secs)
|
||||
battery_new(const char *battery, struct particle *label, long poll_interval_msecs, int battery_scale,
|
||||
long smoothing_secs)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->label = label;
|
||||
m->poll_interval = poll_interval_secs;
|
||||
m->poll_interval = poll_interval_msecs;
|
||||
m->battery_scale = battery_scale;
|
||||
m->smoothing_scale = smoothing_secs * one_sec_in_ns;
|
||||
m->battery = strdup(battery);
|
||||
m->state = STATE_DISCHARGING;
|
||||
m->state = STATE_UNKNOWN;
|
||||
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
|
@ -481,11 +620,29 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
const struct yml_node *name = yml_get_value(node, "name");
|
||||
const struct yml_node *poll_interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *battery_scale = yml_get_value(node, "battery-scale");
|
||||
const struct yml_node *smoothing_secs = yml_get_value(node, "smoothing-secs");
|
||||
|
||||
return battery_new(
|
||||
yml_value_as_string(name),
|
||||
conf_to_particle(c, inherited),
|
||||
poll_interval != NULL ? yml_value_as_int(poll_interval) : 60);
|
||||
return battery_new(yml_value_as_string(name), conf_to_particle(c, inherited),
|
||||
(poll_interval != NULL ? yml_value_as_int(poll_interval) : default_poll_interval),
|
||||
(battery_scale != NULL ? yml_value_as_int(battery_scale) : 1),
|
||||
(smoothing_secs != NULL ? yml_value_as_int(smoothing_secs) : 100));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
const long value = yml_value_as_int(node);
|
||||
|
||||
if (value != 0 && value < min_poll_interval) {
|
||||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -493,7 +650,9 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"name", true, &conf_verify_string},
|
||||
{"poll-interval", false, &conf_verify_int},
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
{"battery-scale", false, &conf_verify_unsigned},
|
||||
{"smoothing-secs", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define LOG_MODULE "clock"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
enum {
|
||||
UPDATE_GRANULARITY_SECONDS,
|
||||
|
@ -22,6 +24,7 @@ struct private {
|
|||
} update_granularity;
|
||||
char *date_format;
|
||||
char *time_format;
|
||||
bool utc;
|
||||
};
|
||||
|
||||
static void
|
||||
|
@ -36,7 +39,7 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "clock";
|
||||
}
|
||||
|
@ -46,7 +49,7 @@ content(struct module *mod)
|
|||
{
|
||||
const struct private *m = mod->private;
|
||||
time_t t = time(NULL);
|
||||
struct tm *tm = localtime(&t);
|
||||
struct tm *tm = m->utc ? gmtime(&t) : localtime(&t);
|
||||
|
||||
char date_str[1024];
|
||||
strftime(date_str, sizeof(date_str), m->date_format, tm);
|
||||
|
@ -55,8 +58,7 @@ content(struct module *mod)
|
|||
strftime(time_str, sizeof(time_str), m->time_format, tm);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){tag_new_string(mod, "time", time_str),
|
||||
tag_new_string(mod, "date", date_str)},
|
||||
.tags = (struct tag *[]){tag_new_string(mod, "time", time_str), tag_new_string(mod, "date", date_str)},
|
||||
.count = 2,
|
||||
};
|
||||
|
||||
|
@ -66,7 +68,6 @@ content(struct module *mod)
|
|||
return exposable;
|
||||
}
|
||||
|
||||
#include <pthread.h>
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
|
@ -74,6 +75,8 @@ run(struct module *mod)
|
|||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
|
||||
int ret = 1;
|
||||
|
||||
while (true) {
|
||||
struct timespec _now;
|
||||
clock_gettime(CLOCK_REALTIME, &_now);
|
||||
|
@ -87,15 +90,12 @@ run(struct module *mod)
|
|||
|
||||
switch (m->update_granularity) {
|
||||
case UPDATE_GRANULARITY_SECONDS: {
|
||||
const struct timeval next_second = {
|
||||
.tv_sec = now.tv_sec + 1,
|
||||
.tv_usec = 0};
|
||||
const struct timeval next_second = {.tv_sec = now.tv_sec + 1, .tv_usec = 0};
|
||||
|
||||
struct timeval _timeout;
|
||||
timersub(&next_second, &now, &_timeout);
|
||||
|
||||
assert(_timeout.tv_sec == 0 ||
|
||||
(_timeout.tv_sec == 1 && _timeout.tv_usec == 0));
|
||||
assert(_timeout.tv_sec == 0 || (_timeout.tv_sec == 1 && _timeout.tv_usec == 0));
|
||||
timeout_ms = _timeout.tv_usec / 1000;
|
||||
break;
|
||||
}
|
||||
|
@ -115,44 +115,44 @@ run(struct module *mod)
|
|||
/* Add 1ms to account for rounding errors */
|
||||
timeout_ms++;
|
||||
|
||||
LOG_DBG("now: %lds %ldµs -> timeout: %dms",
|
||||
now.tv_sec, now.tv_usec, timeout_ms);
|
||||
LOG_DBG("now: %lds %ldµs -> timeout: %dms", now.tv_sec, now.tv_usec, timeout_ms);
|
||||
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
poll(fds, 1, timeout_ms);
|
||||
if (poll(fds, 1, timeout_ms) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
clock_new(struct particle *label, const char *date_format, const char *time_format)
|
||||
clock_new(struct particle *label, const char *date_format, const char *time_format, bool utc)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->label = label;
|
||||
m->date_format = strdup(date_format);
|
||||
m->time_format = strdup(time_format);
|
||||
m->utc = utc;
|
||||
|
||||
static const char *const seconds_formatters[] = {
|
||||
"%c",
|
||||
"%s",
|
||||
"%S",
|
||||
"%T",
|
||||
"%r",
|
||||
"%X",
|
||||
"%c", "%s", "%S", "%T", "%r", "%X",
|
||||
};
|
||||
|
||||
m->update_granularity = UPDATE_GRANULARITY_MINUTES;
|
||||
|
||||
for (size_t i = 0;
|
||||
i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]);
|
||||
i++)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]); i++) {
|
||||
if (strstr(time_format, seconds_formatters[i]) != NULL) {
|
||||
m->update_granularity = UPDATE_GRANULARITY_SECONDS;
|
||||
break;
|
||||
|
@ -160,8 +160,7 @@ clock_new(struct particle *label, const char *date_format, const char *time_form
|
|||
}
|
||||
|
||||
LOG_DBG("using %s update granularity",
|
||||
(m->update_granularity == UPDATE_GRANULARITY_MINUTES
|
||||
? "minutes" : "seconds"));
|
||||
(m->update_granularity == UPDATE_GRANULARITY_MINUTES ? "minutes" : "seconds"));
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
|
@ -178,11 +177,11 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
const struct yml_node *date_format = yml_get_value(node, "date-format");
|
||||
const struct yml_node *time_format = yml_get_value(node, "time-format");
|
||||
const struct yml_node *utc = yml_get_value(node, "utc");
|
||||
|
||||
return clock_new(
|
||||
conf_to_particle(c, inherited),
|
||||
date_format != NULL ? yml_value_as_string(date_format) : "%x",
|
||||
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M");
|
||||
return clock_new(conf_to_particle(c, inherited), date_format != NULL ? yml_value_as_string(date_format) : "%x",
|
||||
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M",
|
||||
utc != NULL ? yml_value_as_bool(utc) : false);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -191,6 +190,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
static const struct attr_info attrs[] = {
|
||||
{"date-format", false, &conf_verify_string},
|
||||
{"time-format", false, &conf_verify_string},
|
||||
{"utc", false, &conf_verify_bool},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
297
modules/cpu.c
Normal file
297
modules/cpu.c
Normal file
|
@ -0,0 +1,297 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOG_MODULE "cpu"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
static const long min_poll_interval = 250;
|
||||
|
||||
struct cpu_stats {
|
||||
uint32_t *prev_cores_idle;
|
||||
uint32_t *prev_cores_nidle;
|
||||
|
||||
uint32_t *cur_cores_idle;
|
||||
uint32_t *cur_cores_nidle;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *template;
|
||||
uint16_t interval;
|
||||
size_t core_count;
|
||||
struct cpu_stats cpu_stats;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
|
||||
m->template->destroy(m->template);
|
||||
free(m->cpu_stats.prev_cores_idle);
|
||||
free(m->cpu_stats.prev_cores_nidle);
|
||||
free(m->cpu_stats.cur_cores_idle);
|
||||
free(m->cpu_stats.cur_cores_nidle);
|
||||
free(m);
|
||||
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "cpu";
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
get_cpu_nb_cores()
|
||||
{
|
||||
int nb_cores = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
LOG_DBG("CPU count: %d", nb_cores);
|
||||
|
||||
return nb_cores;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_proc_stat_line(const char *line, uint32_t *user, uint32_t *nice, uint32_t *system, uint32_t *idle,
|
||||
uint32_t *iowait, uint32_t *irq, uint32_t *softirq, uint32_t *steal, uint32_t *guest,
|
||||
uint32_t *guestnice)
|
||||
{
|
||||
int32_t core_id;
|
||||
if (line[sizeof("cpu") - 1] == ' ') {
|
||||
int read = sscanf(line,
|
||||
"cpu %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu32 " %" SCNu32 " %" SCNu32,
|
||||
user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice);
|
||||
return read == 10;
|
||||
} else {
|
||||
int read = sscanf(line,
|
||||
"cpu%" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32,
|
||||
&core_id, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice);
|
||||
return read == 11;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
get_cpu_usage_percent(const struct cpu_stats *cpu_stats, int8_t core_idx)
|
||||
{
|
||||
uint32_t prev_total = cpu_stats->prev_cores_idle[core_idx + 1] + cpu_stats->prev_cores_nidle[core_idx + 1];
|
||||
|
||||
uint32_t cur_total = cpu_stats->cur_cores_idle[core_idx + 1] + cpu_stats->cur_cores_nidle[core_idx + 1];
|
||||
|
||||
double totald = cur_total - prev_total;
|
||||
double nidled = cpu_stats->cur_cores_nidle[core_idx + 1] - cpu_stats->prev_cores_nidle[core_idx + 1];
|
||||
|
||||
double percent = (nidled * 100) / (totald + 1);
|
||||
return round(percent);
|
||||
}
|
||||
|
||||
static void
|
||||
refresh_cpu_stats(struct cpu_stats *cpu_stats, size_t core_count)
|
||||
{
|
||||
int32_t core = 0;
|
||||
uint32_t user = 0;
|
||||
uint32_t nice = 0;
|
||||
uint32_t system = 0;
|
||||
uint32_t idle = 0;
|
||||
uint32_t iowait = 0;
|
||||
uint32_t irq = 0;
|
||||
uint32_t softirq = 0;
|
||||
uint32_t steal = 0;
|
||||
uint32_t guest = 0;
|
||||
uint32_t guestnice = 0;
|
||||
|
||||
FILE *fp = NULL;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
|
||||
fp = fopen("/proc/stat", "re");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/stat");
|
||||
return;
|
||||
}
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1 && core <= core_count) {
|
||||
if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) {
|
||||
if (!parse_proc_stat_line(line, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest,
|
||||
&guestnice)) {
|
||||
LOG_ERR("unable to parse /proc/stat line");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
cpu_stats->prev_cores_idle[core] = cpu_stats->cur_cores_idle[core];
|
||||
cpu_stats->prev_cores_nidle[core] = cpu_stats->cur_cores_nidle[core];
|
||||
|
||||
cpu_stats->cur_cores_idle[core] = idle + iowait;
|
||||
cpu_stats->cur_cores_nidle[core] = user + nice + system + irq + softirq + steal;
|
||||
|
||||
core++;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
fclose(fp);
|
||||
free(line);
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const size_t list_count = m->core_count + 1;
|
||||
struct exposable *parts[list_count];
|
||||
|
||||
{
|
||||
uint8_t total_usage = get_cpu_usage_percent(&m->cpu_stats, -1);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_int(mod, "id", -1),
|
||||
tag_new_int_range(mod, "cpu", total_usage, 0, 100),
|
||||
},
|
||||
.count = 2,
|
||||
};
|
||||
|
||||
parts[0] = m->template->instantiate(m->template, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m->core_count; i++) {
|
||||
uint8_t core_usage = get_cpu_usage_percent(&m->cpu_stats, i);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_int(mod, "id", i),
|
||||
tag_new_int_range(mod, "cpu", core_usage, 0, 100),
|
||||
},
|
||||
.count = 2,
|
||||
};
|
||||
|
||||
parts[i + 1] = m->template->instantiate(m->template, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return dynlist_exposable_new(parts, list_count, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
|
||||
struct private *p = mod->private;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval);
|
||||
|
||||
if (res < 0) {
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
LOG_ERRNO("unable to poll abort fd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
refresh_cpu_stats(&p->cpu_stats, p->core_count);
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
cpu_new(uint16_t interval, struct particle *template)
|
||||
{
|
||||
uint32_t nb_cores = get_cpu_nb_cores();
|
||||
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->template = template;
|
||||
p->interval = interval;
|
||||
p->core_count = nb_cores;
|
||||
|
||||
p->cpu_stats.prev_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_nidle));
|
||||
p->cpu_stats.prev_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_idle));
|
||||
|
||||
p->cpu_stats.cur_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_nidle));
|
||||
p->cpu_stats.cur_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_idle));
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = p;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return cpu_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < min_poll_interval) {
|
||||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_cpu_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_cpu_iface")));
|
||||
#endif
|
13
modules/dbus.h
Normal file
13
modules/dbus.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
// This header provides an generic interface for different versions of
|
||||
// systemd-sdbus.
|
||||
|
||||
#if defined(HAVE_LIBSYSTEMD)
|
||||
#include <systemd/sd-bus.h>
|
||||
#elif defined(HAVE_LIBELOGIND)
|
||||
#include <elogind/sd-bus.h>
|
||||
#elif defined(HAVE_BASU)
|
||||
#include <basu/sd-bus.h>
|
||||
#endif
|
||||
|
350
modules/disk-io.c
Normal file
350
modules/disk-io.c
Normal file
|
@ -0,0 +1,350 @@
|
|||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <tllist.h>
|
||||
|
||||
#define LOG_MODULE "disk-io"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
static const long min_poll_interval = 250;
|
||||
|
||||
struct device_stats {
|
||||
char *name;
|
||||
bool is_disk;
|
||||
|
||||
uint64_t prev_sectors_read;
|
||||
uint64_t cur_sectors_read;
|
||||
|
||||
uint64_t prev_sectors_written;
|
||||
uint64_t cur_sectors_written;
|
||||
|
||||
uint32_t ios_in_progress;
|
||||
|
||||
bool exists;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
uint16_t interval;
|
||||
tll(struct device_stats *) devices;
|
||||
};
|
||||
|
||||
static bool
|
||||
is_disk(char const *name)
|
||||
{
|
||||
DIR *dir = opendir("/sys/block");
|
||||
if (dir == NULL) {
|
||||
LOG_ERRNO("failed to read /sys/block directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
|
||||
bool found = false;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(name, entry->d_name) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return found;
|
||||
}
|
||||
|
||||
static struct device_stats *
|
||||
new_device_stats(char const *name)
|
||||
{
|
||||
struct device_stats *dev = malloc(sizeof(*dev));
|
||||
dev->name = strdup(name);
|
||||
dev->is_disk = is_disk(name);
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void
|
||||
free_device_stats(struct device_stats *dev)
|
||||
{
|
||||
free(dev->name);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->label->destroy(m->label);
|
||||
tll_foreach(m->devices, it) { free_device_stats(it->item); }
|
||||
tll_free(m->devices);
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "disk-io";
|
||||
}
|
||||
|
||||
static void
|
||||
refresh_device_stats(struct private *m)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
|
||||
fp = fopen("/proc/diskstats", "re");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/diskstats");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Devices may be added or removed during the bar's lifetime, as external
|
||||
* block devices are connected or disconnected from the machine. /proc/diskstats
|
||||
* reports data only for the devices that are currently connected.
|
||||
*
|
||||
* This means that if we have a device that ISN'T in /proc/diskstats, it was
|
||||
* disconnected, and we need to remove it from the list.
|
||||
*
|
||||
* On the other hand, if a device IS in /proc/diskstats, but not in our list, we
|
||||
* must create a new device_stats struct and add it to the list.
|
||||
*
|
||||
* The 'exists' variable is what keep tracks of whether or not /proc/diskstats
|
||||
* is still reporting the device (i.e., it is still connected).
|
||||
*/
|
||||
tll_foreach(m->devices, it) { it->item->exists = false; }
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
/*
|
||||
* For an explanation of the fields below, see
|
||||
* https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
|
||||
*/
|
||||
uint8_t major_number = 0;
|
||||
uint8_t minor_number = 0;
|
||||
char *device_name = NULL;
|
||||
uint32_t completed_reads = 0;
|
||||
uint32_t merged_reads = 0;
|
||||
uint64_t sectors_read = 0;
|
||||
uint32_t reading_time = 0;
|
||||
uint32_t completed_writes = 0;
|
||||
uint32_t merged_writes = 0;
|
||||
uint64_t sectors_written = 0;
|
||||
uint32_t writting_time = 0;
|
||||
uint32_t ios_in_progress = 0;
|
||||
uint32_t io_time = 0;
|
||||
uint32_t io_weighted_time = 0;
|
||||
uint32_t completed_discards = 0;
|
||||
uint32_t merged_discards = 0;
|
||||
uint32_t sectors_discarded = 0;
|
||||
uint32_t discarding_time = 0;
|
||||
uint32_t completed_flushes = 0;
|
||||
uint32_t flushing_time = 0;
|
||||
if (!sscanf(line,
|
||||
" %" SCNu8 " %" SCNu8 " %ms %" SCNu32 " %" SCNu32 " %" SCNu64 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu64 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
|
||||
" %" SCNu32 " %" SCNu32 " %" SCNu32,
|
||||
&major_number, &minor_number, &device_name, &completed_reads, &merged_reads, §ors_read,
|
||||
&reading_time, &completed_writes, &merged_writes, §ors_written, &writting_time,
|
||||
&ios_in_progress, &io_time, &io_weighted_time, &completed_discards, &merged_discards,
|
||||
§ors_discarded, &discarding_time, &completed_flushes, &flushing_time)) {
|
||||
LOG_ERR("unable to parse /proc/diskstats line");
|
||||
free(device_name);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
tll_foreach(m->devices, it)
|
||||
{
|
||||
struct device_stats *dev = it->item;
|
||||
if (strcmp(dev->name, device_name) == 0) {
|
||||
dev->prev_sectors_read = dev->cur_sectors_read;
|
||||
dev->prev_sectors_written = dev->cur_sectors_written;
|
||||
dev->ios_in_progress = ios_in_progress;
|
||||
dev->cur_sectors_read = sectors_read;
|
||||
dev->cur_sectors_written = sectors_written;
|
||||
dev->exists = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
struct device_stats *new_dev = new_device_stats(device_name);
|
||||
new_dev->ios_in_progress = ios_in_progress;
|
||||
new_dev->prev_sectors_read = sectors_read;
|
||||
new_dev->cur_sectors_read = sectors_read;
|
||||
new_dev->prev_sectors_written = sectors_written;
|
||||
new_dev->cur_sectors_written = sectors_written;
|
||||
new_dev->exists = true;
|
||||
tll_push_back(m->devices, new_dev);
|
||||
}
|
||||
|
||||
free(device_name);
|
||||
}
|
||||
|
||||
tll_foreach(m->devices, it)
|
||||
{
|
||||
if (!it->item->exists) {
|
||||
free_device_stats(it->item);
|
||||
tll_remove(m->devices, it);
|
||||
}
|
||||
}
|
||||
exit:
|
||||
fclose(fp);
|
||||
free(line);
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *p = mod->private;
|
||||
uint64_t total_bytes_read = 0;
|
||||
uint64_t total_bytes_written = 0;
|
||||
uint32_t total_ios_in_progress = 0;
|
||||
mtx_lock(&mod->lock);
|
||||
struct exposable *tag_parts[p->devices.length + 1];
|
||||
int i = 0;
|
||||
tll_foreach(p->devices, it)
|
||||
{
|
||||
struct device_stats *dev = it->item;
|
||||
uint64_t bytes_read = (dev->cur_sectors_read - dev->prev_sectors_read) * 512;
|
||||
uint64_t bytes_written = (dev->cur_sectors_written - dev->prev_sectors_written) * 512;
|
||||
|
||||
if (dev->is_disk) {
|
||||
total_bytes_read += bytes_read;
|
||||
total_bytes_written += bytes_written;
|
||||
total_ios_in_progress += dev->ios_in_progress;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]) {
|
||||
tag_new_string(mod, "device", dev->name),
|
||||
tag_new_bool(mod, "is_disk", dev->is_disk),
|
||||
tag_new_int(mod, "read_speed", (bytes_read * 1000) / p->interval),
|
||||
tag_new_int(mod, "write_speed", (bytes_written * 1000) / p->interval),
|
||||
tag_new_int(mod, "ios_in_progress", dev->ios_in_progress),
|
||||
},
|
||||
.count = 5,
|
||||
};
|
||||
tag_parts[i++] = p->label->instantiate(p->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]) {
|
||||
tag_new_string(mod, "device", "Total"),
|
||||
tag_new_bool(mod, "is_disk", true),
|
||||
tag_new_int(mod, "read_speed", (total_bytes_read * 1000) / p->interval),
|
||||
tag_new_int(mod, "write_speed", (total_bytes_written * 1000) / p->interval),
|
||||
tag_new_int(mod, "ios_in_progress", total_ios_in_progress),
|
||||
},
|
||||
.count = 5,
|
||||
};
|
||||
tag_parts[i] = p->label->instantiate(p->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
return dynlist_exposable_new(tag_parts, p->devices.length + 1, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
struct private *p = mod->private;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval);
|
||||
|
||||
if (res < 0) {
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
LOG_ERRNO("unable to poll abort fd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
refresh_device_stats(p);
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
disk_io_new(uint16_t interval, struct particle *label)
|
||||
{
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->label = label;
|
||||
p->interval = interval;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = p;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return disk_io_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval),
|
||||
conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < min_poll_interval) {
|
||||
LOG_ERR("%s: poll-interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_disk_io_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_disk_io_iface")));
|
||||
#endif
|
550
modules/dwl.c
Normal file
550
modules/dwl.c
Normal file
|
@ -0,0 +1,550 @@
|
|||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ARR_LEN(x) (sizeof((x)) / sizeof((x)[0]))
|
||||
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../module.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#define LOG_MODULE "dwl"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
|
||||
struct dwl_tag {
|
||||
int id;
|
||||
char *name;
|
||||
bool selected;
|
||||
bool empty;
|
||||
bool urgent;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
|
||||
char const *monitor;
|
||||
|
||||
unsigned int number_of_tags;
|
||||
char *dwl_info_filename;
|
||||
|
||||
/* dwl data */
|
||||
char *title;
|
||||
char *appid;
|
||||
bool fullscreen;
|
||||
bool floating;
|
||||
bool selmon;
|
||||
tll(struct dwl_tag *) tags;
|
||||
char *layout;
|
||||
};
|
||||
|
||||
enum LINE_MODE {
|
||||
LINE_MODE_0,
|
||||
LINE_MODE_TITLE,
|
||||
LINE_MODE_APPID,
|
||||
LINE_MODE_FULLSCREEN,
|
||||
LINE_MODE_FLOATING,
|
||||
LINE_MODE_SELMON,
|
||||
LINE_MODE_TAGS,
|
||||
LINE_MODE_LAYOUT,
|
||||
};
|
||||
|
||||
static void
|
||||
free_dwl_tag(struct dwl_tag *tag)
|
||||
{
|
||||
free(tag->name);
|
||||
free(tag);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
private->label->destroy(private->label);
|
||||
|
||||
tll_free_and_free(private->tags, free_dwl_tag);
|
||||
free(private->dwl_info_filename);
|
||||
free(private->title);
|
||||
free(private->layout);
|
||||
free(private);
|
||||
|
||||
module_default_destroy(module);
|
||||
}
|
||||
|
||||
static char const *
|
||||
description(const struct module *module)
|
||||
{
|
||||
return "dwl";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *module)
|
||||
{
|
||||
struct private const *private = module->private;
|
||||
mtx_lock(&module->lock);
|
||||
|
||||
size_t i = 0;
|
||||
/* + 1 for `default` tag */
|
||||
struct exposable *exposable[tll_length(private->tags) + 1];
|
||||
tll_foreach(private->tags, it)
|
||||
{
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag*[]){
|
||||
tag_new_string(module, "title", private->title),
|
||||
tag_new_string(module, "appid", private->appid),
|
||||
tag_new_bool(module, "fullscreen", private->fullscreen),
|
||||
tag_new_bool(module, "floating", private->floating),
|
||||
tag_new_bool(module, "selmon", private->selmon),
|
||||
tag_new_string(module, "layout", private->layout),
|
||||
tag_new_int(module, "id", it->item->id),
|
||||
tag_new_string(module, "name", it->item->name),
|
||||
tag_new_bool(module, "selected", it->item->selected),
|
||||
tag_new_bool(module, "empty", it->item->empty),
|
||||
tag_new_bool(module, "urgent", it->item->urgent),
|
||||
},
|
||||
.count = 11,
|
||||
};
|
||||
exposable[i++] = private->label->instantiate(private->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
/* default tag (used for title, layout, etc) */
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag*[]){
|
||||
tag_new_string(module, "title", private->title),
|
||||
tag_new_string(module, "appid", private->appid),
|
||||
tag_new_bool(module, "fullscreen", private->fullscreen),
|
||||
tag_new_bool(module, "floating", private->floating),
|
||||
tag_new_bool(module, "selmon", private->selmon),
|
||||
tag_new_string(module, "layout", private->layout),
|
||||
tag_new_int(module, "id", 0),
|
||||
tag_new_string(module, "name", "0"),
|
||||
tag_new_bool(module, "selected", false),
|
||||
tag_new_bool(module, "empty", true),
|
||||
tag_new_bool(module, "urgent", false),
|
||||
},
|
||||
.count = 11,
|
||||
};
|
||||
exposable[i++] = private->label->instantiate(private->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
|
||||
mtx_unlock(&module->lock);
|
||||
return dynlist_exposable_new(exposable, i, 0, 0);
|
||||
}
|
||||
|
||||
static struct dwl_tag *
|
||||
dwl_tag_from_id(struct private *private, uint32_t id)
|
||||
{
|
||||
tll_foreach(private->tags, it)
|
||||
{
|
||||
if (it->item->id == id)
|
||||
return it->item;
|
||||
}
|
||||
|
||||
assert(false); /* unreachable */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
process_line(char *line, struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
enum LINE_MODE line_mode = LINE_MODE_0;
|
||||
|
||||
/* Remove \n */
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
|
||||
/* Split line by space */
|
||||
size_t index = 1;
|
||||
char *save_pointer = NULL;
|
||||
char *string = strtok_r(line, " ", &save_pointer);
|
||||
while (string != NULL) {
|
||||
/* dwl logs are formatted like this
|
||||
* $1 -> monitor
|
||||
* $2 -> action
|
||||
* $3 -> arg1
|
||||
* $4 -> arg2
|
||||
* ... */
|
||||
|
||||
/* monitor */
|
||||
if (index == 1) {
|
||||
/* Not our monitor */
|
||||
if (strcmp(string, private->monitor) != 0)
|
||||
break;
|
||||
}
|
||||
/* action */
|
||||
else if (index == 2) {
|
||||
if (strcmp(string, "title") == 0) {
|
||||
line_mode = LINE_MODE_TITLE;
|
||||
/* Update the title here, to avoid allocate and free memory on
|
||||
* every iteration (the line is separated by spaces, then we
|
||||
* join it again) a bit suboptimal, isn't it?) */
|
||||
free(private->title);
|
||||
private->title = strdup(save_pointer);
|
||||
break;
|
||||
} else if (strcmp(string, "appid") == 0) {
|
||||
line_mode = LINE_MODE_APPID;
|
||||
/* Update the appid here, same as the title. */
|
||||
free(private->appid);
|
||||
private->appid = strdup(save_pointer);
|
||||
break;
|
||||
} else if (strcmp(string, "fullscreen") == 0)
|
||||
line_mode = LINE_MODE_FULLSCREEN;
|
||||
else if (strcmp(string, "floating") == 0)
|
||||
line_mode = LINE_MODE_FLOATING;
|
||||
else if (strcmp(string, "selmon") == 0)
|
||||
line_mode = LINE_MODE_SELMON;
|
||||
else if (strcmp(string, "tags") == 0)
|
||||
line_mode = LINE_MODE_TAGS;
|
||||
else if (strcmp(string, "layout") == 0)
|
||||
line_mode = LINE_MODE_LAYOUT;
|
||||
else {
|
||||
LOG_WARN("UNKNOWN action, please open an issue on https://codeberg.org/dnkl/yambar");
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* args */
|
||||
else {
|
||||
if (line_mode == LINE_MODE_TAGS) {
|
||||
static uint32_t occupied, selected, client_tags, urgent;
|
||||
static uint32_t *target = NULL;
|
||||
|
||||
/* dwl tags action log are formatted like this
|
||||
* $3 -> occupied
|
||||
* $4 -> tags
|
||||
* $5 -> clientTags (not needed)
|
||||
* $6 -> urgent */
|
||||
if (index == 3)
|
||||
target = &occupied;
|
||||
else if (index == 4)
|
||||
target = &selected;
|
||||
else if (index == 5)
|
||||
target = &client_tags;
|
||||
else if (index == 6)
|
||||
target = &urgent;
|
||||
|
||||
/* No need to check error IMHO */
|
||||
*target = strtoul(string, NULL, 10);
|
||||
|
||||
/* Populate information */
|
||||
if (index == 6) {
|
||||
for (size_t id = 1; id <= private->number_of_tags; ++id) {
|
||||
uint32_t mask = 1 << (id - 1);
|
||||
|
||||
struct dwl_tag *dwl_tag = dwl_tag_from_id(private, id);
|
||||
dwl_tag->selected = mask & selected;
|
||||
dwl_tag->empty = !(mask & occupied);
|
||||
dwl_tag->urgent = mask & urgent;
|
||||
}
|
||||
}
|
||||
} else
|
||||
switch (line_mode) {
|
||||
case LINE_MODE_TITLE:
|
||||
case LINE_MODE_APPID:
|
||||
assert(false); /* unreachable */
|
||||
break;
|
||||
case LINE_MODE_FULLSCREEN:
|
||||
private
|
||||
->fullscreen = (strcmp(string, "0") != 0);
|
||||
break;
|
||||
case LINE_MODE_FLOATING:
|
||||
private
|
||||
->floating = (strcmp(string, "0") != 0);
|
||||
break;
|
||||
case LINE_MODE_SELMON:
|
||||
private
|
||||
->selmon = (strcmp(string, "0") != 0);
|
||||
break;
|
||||
case LINE_MODE_LAYOUT:
|
||||
free(private->layout);
|
||||
private->layout = strdup(string);
|
||||
break;
|
||||
default:;
|
||||
assert(false); /* unreachable */
|
||||
}
|
||||
}
|
||||
|
||||
string = strtok_r(NULL, " ", &save_pointer);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
file_read_content(FILE *file, struct module *module)
|
||||
{
|
||||
static char buffer[1024];
|
||||
|
||||
errno = 0;
|
||||
while (fgets(buffer, ARR_LEN(buffer), file) != NULL)
|
||||
process_line(buffer, module);
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
|
||||
/* Check whether error has been */
|
||||
if (ferror(file) != 0) {
|
||||
LOG_ERRNO("unable to read file's content.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
file_seek_to_last_n_lines(FILE *file, int number_of_lines)
|
||||
{
|
||||
if (number_of_lines == 0 || file == NULL)
|
||||
return;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
|
||||
long position = ftell(file);
|
||||
while (position > 0) {
|
||||
/* Cannot go less than position 0 */
|
||||
if (fseek(file, --position, SEEK_SET) == EINVAL)
|
||||
break;
|
||||
|
||||
if (fgetc(file) == '\n')
|
||||
if (number_of_lines-- == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
run_init(int *inotify_fd, int *inotify_wd, FILE **file, char *dwl_info_filename)
|
||||
{
|
||||
*inotify_fd = inotify_init();
|
||||
if (*inotify_fd == -1) {
|
||||
LOG_ERRNO("unable to create inotify fd.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*inotify_wd = inotify_add_watch(*inotify_fd, dwl_info_filename, IN_MODIFY);
|
||||
if (*inotify_wd == -1) {
|
||||
close(*inotify_fd);
|
||||
LOG_ERRNO("unable to add watch to inotify fd.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
*file = fopen(dwl_info_filename, "re");
|
||||
if (*file == NULL) {
|
||||
inotify_rm_watch(*inotify_fd, *inotify_wd);
|
||||
close(*inotify_fd);
|
||||
LOG_ERRNO("unable to open file.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_clean(int inotify_fd, int inotify_wd, FILE *file)
|
||||
{
|
||||
if (inotify_fd != -1) {
|
||||
if (inotify_wd != -1)
|
||||
inotify_rm_watch(inotify_fd, inotify_wd);
|
||||
close(inotify_fd);
|
||||
}
|
||||
|
||||
if (file != NULL) {
|
||||
if (fclose(file) == EOF) {
|
||||
LOG_ERRNO("unable to close file.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int
|
||||
run(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
|
||||
/* Ugly, but I didn't find better way for waiting
|
||||
* the monitor's name to be set */
|
||||
do {
|
||||
private->monitor = module->bar->output_name(module->bar);
|
||||
usleep(50);
|
||||
} while (private->monitor == NULL);
|
||||
|
||||
int inotify_fd = -1, inotify_wd = -1;
|
||||
FILE *file = NULL;
|
||||
if (run_init(&inotify_fd, &inotify_wd, &file, private->dwl_info_filename) != 0)
|
||||
return 1;
|
||||
|
||||
/* Dwl output is 6 lines per monitor, so let's assume that nobody has
|
||||
* more than 5 monitors (6 * 5 = 30) */
|
||||
mtx_lock(&module->lock);
|
||||
file_seek_to_last_n_lines(file, 30);
|
||||
if (file_read_content(file, module) != 0) {
|
||||
mtx_unlock(&module->lock);
|
||||
return run_clean(inotify_fd, inotify_wd, file);
|
||||
}
|
||||
mtx_unlock(&module->lock);
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
(struct pollfd){.fd = module->abort_fd, .events = POLLIN},
|
||||
(struct pollfd){.fd = inotify_fd, .events = POLLIN},
|
||||
};
|
||||
|
||||
if (poll(fds, ARR_LEN(fds), -1) == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("unable to poll.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
/* fds[1] (inotify_fd) must be POLLIN otherwise issue happen'd */
|
||||
if (!(fds[1].revents & POLLIN)) {
|
||||
LOG_ERR("expected POLLIN revent");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Block until event */
|
||||
static char buffer[1024];
|
||||
ssize_t length = read(inotify_fd, buffer, ARR_LEN(buffer));
|
||||
|
||||
if (length == 0)
|
||||
break;
|
||||
|
||||
if (length == -1) {
|
||||
if (errno == EAGAIN)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("unable to read %s", private->dwl_info_filename);
|
||||
break;
|
||||
}
|
||||
|
||||
mtx_lock(&module->lock);
|
||||
if (file_read_content(file, module) != 0) {
|
||||
mtx_unlock(&module->lock);
|
||||
break;
|
||||
}
|
||||
mtx_unlock(&module->lock);
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
}
|
||||
|
||||
return run_clean(inotify_fd, inotify_wd, file);
|
||||
}
|
||||
|
||||
static struct module *
|
||||
dwl_new(struct particle *label, int number_of_tags, struct yml_node const *name_of_tags, char const *dwl_info_filename)
|
||||
{
|
||||
struct private *private = calloc(1, sizeof(struct private));
|
||||
private->label = label;
|
||||
private->number_of_tags = number_of_tags;
|
||||
private->dwl_info_filename = strdup(dwl_info_filename);
|
||||
|
||||
struct yml_list_iter list = {0};
|
||||
if (name_of_tags)
|
||||
list = yml_list_iter(name_of_tags);
|
||||
|
||||
for (int i = 1; i <= number_of_tags; i++) {
|
||||
struct dwl_tag *dwl_tag = calloc(1, sizeof(struct dwl_tag));
|
||||
dwl_tag->id = i;
|
||||
if (list.node) {
|
||||
dwl_tag->name = strdup(yml_value_as_string(list.node));
|
||||
yml_list_next(&list);
|
||||
} else if (asprintf(&dwl_tag->name, "%d", i) < 0) {
|
||||
LOG_ERRNO("asprintf");
|
||||
}
|
||||
tll_push_back(private->tags, dwl_tag);
|
||||
}
|
||||
|
||||
struct module *module = module_common_new();
|
||||
module->private = private;
|
||||
module->run = &run;
|
||||
module->destroy = &destroy;
|
||||
module->content = &content;
|
||||
module->description = &description;
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(struct yml_node const *node, struct conf_inherit inherited)
|
||||
{
|
||||
struct yml_node const *content = yml_get_value(node, "content");
|
||||
struct yml_node const *number_of_tags = yml_get_value(node, "number-of-tags");
|
||||
struct yml_node const *name_of_tags = yml_get_value(node, "name-of-tags");
|
||||
struct yml_node const *dwl_info_filename = yml_get_value(node, "dwl-info-filename");
|
||||
|
||||
return dwl_new(conf_to_particle(content, inherited), yml_value_as_int(number_of_tags), name_of_tags,
|
||||
yml_value_as_string(dwl_info_filename));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_names(keychain_t *keychain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_list(node)) {
|
||||
LOG_ERR("%s: %s is not a list", conf_err_prefix(keychain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
return conf_verify_list(keychain, node, &conf_verify_string);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *keychain, struct yml_node const *node)
|
||||
{
|
||||
|
||||
static struct attr_info const attrs[] = {
|
||||
{"number-of-tags", true, &conf_verify_unsigned},
|
||||
{"name-of-tags", false, &verify_names},
|
||||
{"dwl-info-filename", true, &conf_verify_string},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
if (!conf_verify_dict(keychain, node, attrs))
|
||||
return false;
|
||||
|
||||
/* No need to check whether is `number_of_tags` is a int
|
||||
* because `conf_verify_unsigned` already did it */
|
||||
struct yml_node const *ntags_key = yml_get_key(node, "number-of-tags");
|
||||
struct yml_node const *value = yml_get_value(node, "number-of-tags");
|
||||
int number_of_tags = yml_value_as_int(value);
|
||||
if (number_of_tags == 0) {
|
||||
LOG_ERR("%s: %s must not be 0", conf_err_prefix(keychain, ntags_key), yml_value_as_string(ntags_key));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct yml_node const *key = yml_get_key(node, "name-of-tags");
|
||||
value = yml_get_value(node, "name-of-tags");
|
||||
if (value && yml_list_length(value) != number_of_tags) {
|
||||
LOG_ERR("%s: %s must have the same number of elements that %s", conf_err_prefix(keychain, key),
|
||||
yml_value_as_string(key), yml_value_as_string(ntags_key));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No need to check whether is `dwl_info_filename` is a string
|
||||
* because `conf_verify_string` already did it */
|
||||
key = yml_get_key(node, "dwl-info-filename");
|
||||
value = yml_get_value(node, "dwl-info-filename");
|
||||
if (strlen(yml_value_as_string(value)) == 0) {
|
||||
LOG_ERR("%s: %s must not be empty", conf_err_prefix(keychain, key), yml_value_as_string(key));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct module_iface const module_dwl_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern struct module_iface const iface __attribute__((weak, alias("module_dwl_iface")));
|
||||
#endif
|
666
modules/foreign-toplevel.c
Normal file
666
modules/foreign-toplevel.c
Normal file
|
@ -0,0 +1,666 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include <tllist.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#define LOG_MODULE "foreign-toplevel"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#include "wlr-foreign-toplevel-management-unstable-v1.h"
|
||||
#include "xdg-output-unstable-v1.h"
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
static const int required_manager_interface_version = 2;
|
||||
|
||||
struct output {
|
||||
struct module *mod;
|
||||
|
||||
uint32_t wl_name;
|
||||
struct wl_output *wl_output;
|
||||
struct zxdg_output_v1 *xdg_output;
|
||||
|
||||
char *name;
|
||||
};
|
||||
|
||||
struct toplevel {
|
||||
struct module *mod;
|
||||
struct zwlr_foreign_toplevel_handle_v1 *handle;
|
||||
|
||||
char *app_id;
|
||||
char *title;
|
||||
|
||||
bool maximized;
|
||||
bool minimized;
|
||||
bool activated;
|
||||
bool fullscreen;
|
||||
|
||||
tll(const struct output *) outputs;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *template;
|
||||
uint32_t manager_wl_name;
|
||||
struct zwlr_foreign_toplevel_manager_v1 *manager;
|
||||
struct zxdg_output_manager_v1 *xdg_output_manager;
|
||||
|
||||
bool all_monitors;
|
||||
tll(struct toplevel) toplevels;
|
||||
tll(struct output) outputs;
|
||||
};
|
||||
|
||||
static void
|
||||
output_free(struct output *output)
|
||||
{
|
||||
free(output->name);
|
||||
if (output->xdg_output != NULL)
|
||||
zxdg_output_v1_destroy(output->xdg_output);
|
||||
if (output->wl_output != NULL)
|
||||
wl_output_release(output->wl_output);
|
||||
}
|
||||
|
||||
static void
|
||||
toplevel_free(struct toplevel *top)
|
||||
{
|
||||
if (top->handle != NULL)
|
||||
zwlr_foreign_toplevel_handle_v1_destroy(top->handle);
|
||||
|
||||
free(top->app_id);
|
||||
free(top->title);
|
||||
tll_free(top->outputs);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->template->destroy(m->template);
|
||||
|
||||
assert(tll_length(m->toplevels) == 0);
|
||||
assert(tll_length(m->outputs) == 0);
|
||||
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "toplevel";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const size_t toplevel_count = tll_length(m->toplevels);
|
||||
size_t show_count = 0;
|
||||
struct exposable *toplevels[toplevel_count];
|
||||
|
||||
const char *current_output = mod->bar->output_name(mod->bar);
|
||||
|
||||
tll_foreach(m->toplevels, it)
|
||||
{
|
||||
const struct toplevel *top = &it->item;
|
||||
|
||||
bool show = false;
|
||||
|
||||
if (m->all_monitors)
|
||||
show = true;
|
||||
else if (current_output != NULL) {
|
||||
tll_foreach(top->outputs, it2)
|
||||
{
|
||||
const struct output *output = it2->item;
|
||||
if (output->name != NULL && strcmp(output->name, current_output) == 0) {
|
||||
show = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!show)
|
||||
continue;
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_string(mod, "app-id", it->item.app_id),
|
||||
tag_new_string(mod, "title", it->item.title),
|
||||
tag_new_bool(mod, "maximized", it->item.maximized),
|
||||
tag_new_bool(mod, "minimized", it->item.minimized),
|
||||
tag_new_bool(mod, "activated", it->item.activated),
|
||||
tag_new_bool(mod, "fullscreen", it->item.fullscreen),
|
||||
},
|
||||
.count = 6,
|
||||
};
|
||||
|
||||
toplevels[show_count++] = m->template->instantiate(m->template, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return dynlist_exposable_new(toplevels, show_count, 0, 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
|
||||
{
|
||||
if (version >= wanted)
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name)
|
||||
{
|
||||
struct output *output = data;
|
||||
struct module *mod = output->mod;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
{
|
||||
free(output->name);
|
||||
output->name = name != NULL ? strdup(name) : NULL;
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description)
|
||||
{
|
||||
}
|
||||
|
||||
static struct zxdg_output_v1_listener xdg_output_listener = {
|
||||
.logical_position = xdg_output_handle_logical_position,
|
||||
.logical_size = xdg_output_handle_logical_size,
|
||||
.done = xdg_output_handle_done,
|
||||
.name = xdg_output_handle_name,
|
||||
.description = xdg_output_handle_description,
|
||||
};
|
||||
|
||||
static void
|
||||
title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
|
||||
mtx_lock(&top->mod->lock);
|
||||
{
|
||||
free(top->title);
|
||||
top->title = title != NULL ? strdup(title) : NULL;
|
||||
}
|
||||
mtx_unlock(&top->mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
|
||||
mtx_lock(&top->mod->lock);
|
||||
{
|
||||
free(top->app_id);
|
||||
top->app_id = app_id != NULL ? strdup(app_id) : NULL;
|
||||
}
|
||||
mtx_unlock(&top->mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
struct module *mod = top->mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const struct output *output = NULL;
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
if (it->item.wl_output == wl_output) {
|
||||
output = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (output == NULL) {
|
||||
LOG_ERR("output-enter event on untracked output");
|
||||
goto out;
|
||||
}
|
||||
|
||||
tll_foreach(top->outputs, it)
|
||||
{
|
||||
if (it->item == output) {
|
||||
LOG_ERR("output-enter event on output we're already on");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("mapped: %s:%s on %s", top->app_id, top->title, output->name);
|
||||
tll_push_back(top->outputs, output);
|
||||
|
||||
out:
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
struct module *mod = top->mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const struct output *output = NULL;
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
if (it->item.wl_output == wl_output) {
|
||||
output = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (output == NULL) {
|
||||
LOG_ERR("output-leave event on untracked output");
|
||||
goto out;
|
||||
}
|
||||
|
||||
bool output_removed = false;
|
||||
tll_foreach(top->outputs, it)
|
||||
{
|
||||
if (it->item == output) {
|
||||
LOG_DBG("unmapped: %s:%s from %s", top->app_id, top->title, output->name);
|
||||
tll_remove(top->outputs, it);
|
||||
output_removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!output_removed) {
|
||||
LOG_ERR("output-leave event on an output we're not on");
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *states)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
|
||||
bool maximized = false;
|
||||
bool minimized = false;
|
||||
bool activated = false;
|
||||
bool fullscreen = false;
|
||||
|
||||
enum zwlr_foreign_toplevel_handle_v1_state *state;
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
switch (*state) {
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED:
|
||||
maximized = true;
|
||||
break;
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED:
|
||||
minimized = true;
|
||||
break;
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED:
|
||||
activated = true;
|
||||
break;
|
||||
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN:
|
||||
fullscreen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mtx_lock(&top->mod->lock);
|
||||
{
|
||||
top->maximized = maximized;
|
||||
top->minimized = minimized;
|
||||
top->activated = activated;
|
||||
top->fullscreen = fullscreen;
|
||||
}
|
||||
mtx_unlock(&top->mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
const struct bar *bar = top->mod->bar;
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
static void
|
||||
closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||
{
|
||||
struct toplevel *top = data;
|
||||
struct module *mod = top->mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
tll_foreach(m->toplevels, it)
|
||||
{
|
||||
if (it->item.handle == handle) {
|
||||
toplevel_free(top);
|
||||
tll_remove(m->toplevels, it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
static void
|
||||
parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_listener = {
|
||||
.title = &title,
|
||||
.app_id = &app_id,
|
||||
.output_enter = &output_enter,
|
||||
.output_leave = &output_leave,
|
||||
.state = &state,
|
||||
.done = &done,
|
||||
.closed = &closed,
|
||||
.parent = &parent,
|
||||
};
|
||||
|
||||
static void
|
||||
toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *handle)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
struct toplevel toplevel = {
|
||||
.mod = mod,
|
||||
.handle = handle,
|
||||
};
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
{
|
||||
tll_push_back(m->toplevels, toplevel);
|
||||
|
||||
zwlr_foreign_toplevel_handle_v1_add_listener(handle, &toplevel_listener, &tll_back(m->toplevels));
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
assert(m->manager == manager);
|
||||
zwlr_foreign_toplevel_manager_v1_destroy(m->manager);
|
||||
m->manager = NULL;
|
||||
}
|
||||
|
||||
static const struct zwlr_foreign_toplevel_manager_v1_listener manager_listener = {
|
||||
.toplevel = &toplevel,
|
||||
.finished = &finished,
|
||||
};
|
||||
|
||||
static void
|
||||
output_xdg_output(struct output *output)
|
||||
{
|
||||
struct private *m = output->mod->private;
|
||||
|
||||
if (m->xdg_output_manager == NULL)
|
||||
return;
|
||||
if (output->xdg_output != NULL)
|
||||
return;
|
||||
|
||||
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(m->xdg_output_manager, output->wl_output);
|
||||
zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
|
||||
if (!verify_iface_version(interface, version, required_manager_interface_version))
|
||||
return;
|
||||
|
||||
m->manager_wl_name = name;
|
||||
}
|
||||
|
||||
else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
const uint32_t required = 3;
|
||||
if (!verify_iface_version(interface, version, required))
|
||||
return;
|
||||
|
||||
struct output output = {
|
||||
.mod = mod,
|
||||
.wl_name = name,
|
||||
.wl_output = wl_registry_bind(registry, name, &wl_output_interface, required),
|
||||
};
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
tll_push_back(m->outputs, output);
|
||||
output_xdg_output(&tll_back(m->outputs));
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
|
||||
const uint32_t required = 2;
|
||||
if (!verify_iface_version(interface, version, required))
|
||||
return;
|
||||
|
||||
m->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, required);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
tll_foreach(m->outputs, it) output_xdg_output(&it->item);
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
const struct output *output = &it->item;
|
||||
if (output->wl_name == name) {
|
||||
|
||||
/* Loop all toplevels */
|
||||
tll_foreach(m->toplevels, it2)
|
||||
{
|
||||
|
||||
/* And remove this output from their list of tracked
|
||||
* outputs */
|
||||
tll_foreach(it2->item.outputs, it3)
|
||||
{
|
||||
if (it3->item == output) {
|
||||
tll_remove(it2->item.outputs, it3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tll_remove(m->outputs, it);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
mtx_unlock(&mod->lock);
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = &handle_global,
|
||||
.global_remove = &handle_global_remove,
|
||||
};
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
int ret = -1;
|
||||
|
||||
struct wl_display *display = NULL;
|
||||
struct wl_registry *registry = NULL;
|
||||
|
||||
if ((display = wl_display_connect(NULL)) == NULL) {
|
||||
LOG_ERR("no Wayland compositor running");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((registry = wl_display_get_registry(display)) == NULL
|
||||
|| wl_registry_add_listener(registry, ®istry_listener, mod) != 0) {
|
||||
LOG_ERR("failed to get Wayland registry");
|
||||
goto out;
|
||||
}
|
||||
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
if (m->manager_wl_name == 0) {
|
||||
LOG_ERR("compositor does not implement the foreign-toplevel-manager interface");
|
||||
goto out;
|
||||
}
|
||||
|
||||
m->manager = wl_registry_bind(registry, m->manager_wl_name, &zwlr_foreign_toplevel_manager_v1_interface,
|
||||
required_manager_interface_version);
|
||||
|
||||
zwlr_foreign_toplevel_manager_v1_add_listener(m->manager, &manager_listener, mod);
|
||||
|
||||
while (true) {
|
||||
wl_display_flush(display);
|
||||
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = wl_display_get_fd(display), .events = POLLIN},
|
||||
};
|
||||
|
||||
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & (POLLIN | POLLHUP)) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_ERR("disconnected from the Wayland compositor");
|
||||
break;
|
||||
}
|
||||
|
||||
assert(fds[1].revents & POLLIN);
|
||||
wl_display_dispatch(display);
|
||||
}
|
||||
|
||||
out:
|
||||
tll_foreach(m->toplevels, it)
|
||||
{
|
||||
toplevel_free(&it->item);
|
||||
tll_remove(m->toplevels, it);
|
||||
}
|
||||
|
||||
tll_foreach(m->outputs, it)
|
||||
{
|
||||
output_free(&it->item);
|
||||
tll_remove(m->outputs, it);
|
||||
}
|
||||
|
||||
if (m->xdg_output_manager != NULL)
|
||||
zxdg_output_manager_v1_destroy(m->xdg_output_manager);
|
||||
if (m->manager != NULL)
|
||||
zwlr_foreign_toplevel_manager_v1_destroy(m->manager);
|
||||
if (registry != NULL)
|
||||
wl_registry_destroy(registry);
|
||||
if (display != NULL)
|
||||
wl_display_disconnect(display);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
ftop_new(struct particle *label, bool all_monitors)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->template = label;
|
||||
m->all_monitors = all_monitors;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
const struct yml_node *all_monitors = yml_get_value(node, "all-monitors");
|
||||
|
||||
return ftop_new(conf_to_particle(c, inherited), all_monitors != NULL ? yml_value_as_bool(all_monitors) : false);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"all-monitors", false, &conf_verify_bool},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_foreign_toplevel_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_foreign_toplevel_iface")));
|
||||
#endif
|
|
@ -1,15 +1,15 @@
|
|||
#include "i3-common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#endif
|
||||
|
||||
#include <json-c/json_tokener.h>
|
||||
|
@ -19,7 +19,7 @@
|
|||
#include "../log.h"
|
||||
|
||||
#if defined(ENABLE_X11)
|
||||
#include "../xcb.h"
|
||||
#include "../xcb.h"
|
||||
#endif
|
||||
|
||||
#include "i3-ipc.h"
|
||||
|
@ -41,14 +41,11 @@ get_socket_address_x11(struct sockaddr_un *addr)
|
|||
xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH");
|
||||
assert(atom != XCB_ATOM_NONE);
|
||||
|
||||
xcb_get_property_cookie_t cookie
|
||||
= xcb_get_property_unchecked(
|
||||
conn, false, screen->root, atom,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path));
|
||||
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(conn, false, screen->root, atom,
|
||||
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path));
|
||||
|
||||
xcb_generic_error_t *err = NULL;
|
||||
xcb_get_property_reply_t *reply =
|
||||
xcb_get_property_reply(conn, cookie, &err);
|
||||
xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, cookie, &err);
|
||||
bool ret = false;
|
||||
|
||||
if (err != NULL) {
|
||||
|
@ -102,11 +99,7 @@ bool
|
|||
i3_send_pkg(int sock, int cmd, char *data)
|
||||
{
|
||||
const size_t size = data != NULL ? strlen(data) : 0;
|
||||
const i3_ipc_header_t hdr = {
|
||||
.magic = I3_IPC_MAGIC,
|
||||
.size = size,
|
||||
.type = cmd
|
||||
};
|
||||
const i3_ipc_header_t hdr = {.magic = I3_IPC_MAGIC, .size = size, .type = cmd};
|
||||
|
||||
if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr))
|
||||
return false;
|
||||
|
@ -120,8 +113,7 @@ i3_send_pkg(int sock, int cmd, char *data)
|
|||
}
|
||||
|
||||
bool
|
||||
i3_receive_loop(int abort_fd, int sock,
|
||||
const struct i3_ipc_callbacks *cbs, void *data)
|
||||
i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void *data)
|
||||
{
|
||||
/* Initial reply typically requires a couple of KB. But we often
|
||||
* need more later. For example, switching workspaces can result
|
||||
|
@ -133,10 +125,7 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
bool err = false;
|
||||
|
||||
while (!err) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = abort_fd, .events = POLLIN},
|
||||
{.fd = sock, .events = POLLIN}
|
||||
};
|
||||
struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}, {.fd = sock, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, 2, -1);
|
||||
if (res <= 0) {
|
||||
|
@ -159,13 +148,11 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
|
||||
/* Grow receive buffer, if necessary */
|
||||
if (buf_idx == reply_buf_size) {
|
||||
LOG_DBG("growing reply buffer: %zu -> %zu",
|
||||
reply_buf_size, reply_buf_size * 2);
|
||||
LOG_DBG("growing reply buffer: %zu -> %zu", reply_buf_size, reply_buf_size * 2);
|
||||
|
||||
char *new_buf = realloc(buf, reply_buf_size * 2);
|
||||
if (new_buf == NULL) {
|
||||
LOG_ERR("failed to grow reply buffer from %zu to %zu bytes",
|
||||
reply_buf_size, reply_buf_size * 2);
|
||||
LOG_ERR("failed to grow reply buffer from %zu to %zu bytes", reply_buf_size, reply_buf_size * 2);
|
||||
err = true;
|
||||
break;
|
||||
}
|
||||
|
@ -188,10 +175,8 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
while (!err && buf_idx >= sizeof(i3_ipc_header_t)) {
|
||||
const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf;
|
||||
if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) {
|
||||
LOG_ERR(
|
||||
"i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"",
|
||||
(int)sizeof(hdr->magic), I3_IPC_MAGIC,
|
||||
(int)sizeof(hdr->magic), hdr->magic);
|
||||
LOG_ERR("i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"", (int)sizeof(hdr->magic),
|
||||
I3_IPC_MAGIC, (int)sizeof(hdr->magic), hdr->magic);
|
||||
|
||||
err = true;
|
||||
break;
|
||||
|
@ -210,10 +195,10 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
char json_str[hdr->size + 1];
|
||||
memcpy(json_str, &buf[sizeof(*hdr)], hdr->size);
|
||||
json_str[hdr->size] = '\0';
|
||||
//printf("raw: %s\n", json_str);
|
||||
// printf("raw: %s\n", json_str);
|
||||
LOG_DBG("raw: %s\n", json_str);
|
||||
|
||||
//json_tokener *tokener = json_tokener_new();
|
||||
// json_tokener *tokener = json_tokener_new();
|
||||
struct json_object *json = json_tokener_parse(json_str);
|
||||
if (json == NULL) {
|
||||
LOG_ERR("failed to parse json");
|
||||
|
@ -262,13 +247,13 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
break;
|
||||
#endif
|
||||
/* Sway extensions */
|
||||
case 100: /* IPC_GET_INPUTS */
|
||||
case 100: /* IPC_GET_INPUTS */
|
||||
pkt_handler = cbs->reply_inputs;
|
||||
break;
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
case I3_IPC_EVENT_WORKSPACE:
|
||||
pkt_handler = cbs->event_workspace;
|
||||
|
@ -295,7 +280,7 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
pkt_handler = cbs->event_tick;
|
||||
break;
|
||||
|
||||
/* Sway extensions */
|
||||
/* Sway extensions */
|
||||
#define SWAY_IPC_EVENT_INPUT ((1u << 31) | 21)
|
||||
case SWAY_IPC_EVENT_INPUT:
|
||||
pkt_handler = cbs->event_input;
|
||||
|
@ -309,7 +294,7 @@ i3_receive_loop(int abort_fd, int sock,
|
|||
}
|
||||
|
||||
if (pkt_handler != NULL)
|
||||
err = !pkt_handler(hdr->type, json, data);
|
||||
err = !pkt_handler(sock, hdr->type, json, data);
|
||||
else
|
||||
LOG_DBG("no handler for reply/event %d; ignoring", hdr->type);
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <json-c/json_util.h>
|
||||
|
@ -11,7 +11,7 @@
|
|||
bool i3_get_socket_address(struct sockaddr_un *addr);
|
||||
bool i3_send_pkg(int sock, int cmd, char *data);
|
||||
|
||||
typedef bool (*i3_ipc_callback_t)(int type, const struct json_object *json, void *data);
|
||||
typedef bool (*i3_ipc_callback_t)(int sock, int type, const struct json_object *json, void *data);
|
||||
|
||||
struct i3_ipc_callbacks {
|
||||
void (*burst_done)(void *data);
|
||||
|
@ -43,6 +43,4 @@ struct i3_ipc_callbacks {
|
|||
i3_ipc_callback_t event_input;
|
||||
};
|
||||
|
||||
bool i3_receive_loop(
|
||||
int abort_fd, int sock,
|
||||
const struct i3_ipc_callbacks *callbacks, void *data);
|
||||
bool i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *callbacks, void *data);
|
||||
|
|
586
modules/i3.c
586
modules/i3.c
|
@ -1,27 +1,27 @@
|
|||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <tllist.h>
|
||||
|
||||
#define LOG_MODULE "i3"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#include "i3-ipc.h"
|
||||
#include "i3-common.h"
|
||||
#include "i3-ipc.h"
|
||||
|
||||
enum sort_mode {SORT_NONE, SORT_ASCENDING, SORT_DESCENDING};
|
||||
enum sort_mode { SORT_NONE, SORT_NATIVE, SORT_ASCENDING, SORT_DESCENDING };
|
||||
|
||||
struct ws_content {
|
||||
char *name;
|
||||
|
@ -29,13 +29,16 @@ struct ws_content {
|
|||
};
|
||||
|
||||
struct workspace {
|
||||
int id;
|
||||
char *name;
|
||||
int name_as_int; /* -1 is name is not a decimal number */
|
||||
int name_as_int; /* -1 if name is not a decimal number */
|
||||
bool persistent;
|
||||
|
||||
char *output;
|
||||
bool visible;
|
||||
bool focused;
|
||||
bool urgent;
|
||||
bool empty;
|
||||
|
||||
struct {
|
||||
unsigned id;
|
||||
|
@ -45,7 +48,8 @@ struct workspace {
|
|||
} window;
|
||||
};
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
int left_spacing;
|
||||
int right_spacing;
|
||||
|
||||
|
@ -58,22 +62,61 @@ struct private {
|
|||
size_t count;
|
||||
} ws_content;
|
||||
|
||||
bool strip_workspace_numbers;
|
||||
enum sort_mode sort_mode;
|
||||
tll(struct workspace) workspaces;
|
||||
|
||||
size_t persistent_count;
|
||||
char **persistent_workspaces;
|
||||
};
|
||||
|
||||
static int
|
||||
workspace_name_as_int(const char *name)
|
||||
{
|
||||
int name_as_int = 0;
|
||||
|
||||
/* First check for N:name pattern (set $ws1 “1:foobar”) */
|
||||
const char *colon = strchr(name, ':');
|
||||
if (colon != NULL) {
|
||||
for (const char *p = name; p < colon; p++) {
|
||||
if (!(*p >= '0' && *p < '9'))
|
||||
return -1;
|
||||
|
||||
name_as_int *= 10;
|
||||
name_as_int += *p - '0';
|
||||
}
|
||||
|
||||
return name_as_int;
|
||||
}
|
||||
|
||||
/* Then, if the name is a number *only* (set $ws1 1) */
|
||||
for (const char *p = name; *p != '\0'; p++) {
|
||||
if (!(*p >= '0' && *p <= '9'))
|
||||
return -1;
|
||||
|
||||
name_as_int *= 10;
|
||||
name_as_int += *p - '0';
|
||||
}
|
||||
|
||||
return name_as_int;
|
||||
}
|
||||
|
||||
static bool
|
||||
workspace_from_json(const struct json_object *json, struct workspace *ws)
|
||||
{
|
||||
/* Always present */
|
||||
struct json_object *name, *output;
|
||||
if (!json_object_object_get_ex(json, "name", &name) ||
|
||||
!json_object_object_get_ex(json, "output", &output))
|
||||
{
|
||||
LOG_ERR("workspace reply/event without 'name' and/or 'output' property");
|
||||
struct json_object *id, *name, *output;
|
||||
if (!json_object_object_get_ex(json, "id", &id) || !json_object_object_get_ex(json, "name", &name)
|
||||
|| !json_object_object_get_ex(json, "output", &output)) {
|
||||
LOG_ERR("workspace reply/event without 'name' and/or 'output' "
|
||||
"properties");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Sway only */
|
||||
struct json_object *focus = NULL;
|
||||
json_object_object_get_ex(json, "focus", &focus);
|
||||
|
||||
/* Optional */
|
||||
struct json_object *visible = NULL, *focused = NULL, *urgent = NULL;
|
||||
json_object_object_get_ex(json, "visible", &visible);
|
||||
|
@ -82,24 +125,21 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
|
|||
|
||||
const char *name_as_string = json_object_get_string(name);
|
||||
|
||||
int name_as_int = 0;
|
||||
for (const char *p = name_as_string; *p != '\0'; p++) {
|
||||
if (!(*p >= '0' && *p <= '9')) {
|
||||
name_as_int = -1;
|
||||
break;
|
||||
}
|
||||
const size_t node_count = focus != NULL ? json_object_array_length(focus) : 0;
|
||||
|
||||
name_as_int *= 10;
|
||||
name_as_int += *p - '0';
|
||||
}
|
||||
const bool is_empty = node_count == 0;
|
||||
int name_as_int = workspace_name_as_int(name_as_string);
|
||||
|
||||
*ws = (struct workspace) {
|
||||
*ws = (struct workspace){
|
||||
.id = json_object_get_int(id),
|
||||
.name = strdup(name_as_string),
|
||||
.name_as_int = name_as_int,
|
||||
.persistent = false,
|
||||
.output = strdup(json_object_get_string(output)),
|
||||
.visible = json_object_get_boolean(visible),
|
||||
.focused = json_object_get_boolean(focused),
|
||||
.urgent = json_object_get_boolean(urgent),
|
||||
.empty = is_empty && json_object_get_boolean(focused),
|
||||
.window = {.title = NULL, .pid = -1},
|
||||
};
|
||||
|
||||
|
@ -107,22 +147,36 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
|
|||
}
|
||||
|
||||
static void
|
||||
workspace_free(struct workspace *ws)
|
||||
workspace_free_persistent(struct workspace *ws)
|
||||
{
|
||||
free(ws->name);
|
||||
free(ws->output);
|
||||
ws->output = NULL;
|
||||
free(ws->window.title);
|
||||
ws->window.title = NULL;
|
||||
free(ws->window.application);
|
||||
ws->window.application = NULL;
|
||||
ws->id = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
workspaces_free(struct private *m)
|
||||
workspace_free(struct workspace *ws)
|
||||
{
|
||||
tll_foreach(m->workspaces, it)
|
||||
workspace_free(&it->item);
|
||||
tll_free(m->workspaces);
|
||||
workspace_free_persistent(ws);
|
||||
free(ws->name);
|
||||
ws->name = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
workspaces_free(struct private *m, bool free_persistent)
|
||||
{
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (free_persistent || !it->item.persistent) {
|
||||
workspace_free(&it->item);
|
||||
tll_remove(m->workspaces, it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
workspace_add(struct private *m, struct workspace ws)
|
||||
|
@ -132,9 +186,26 @@ workspace_add(struct private *m, struct workspace ws)
|
|||
tll_push_back(m->workspaces, ws);
|
||||
return;
|
||||
|
||||
case SORT_NATIVE:
|
||||
if (ws.name_as_int >= 0) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (it->item.name_as_int < 0)
|
||||
continue;
|
||||
if (it->item.name_as_int > ws.name_as_int) {
|
||||
tll_insert_before(m->workspaces, it, ws);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tll_push_back(m->workspaces, ws);
|
||||
return;
|
||||
|
||||
case SORT_ASCENDING:
|
||||
if (ws.name_as_int >= 0) {
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (it->item.name_as_int < 0)
|
||||
continue;
|
||||
if (it->item.name_as_int > ws.name_as_int) {
|
||||
|
@ -143,10 +214,9 @@ workspace_add(struct private *m, struct workspace ws)
|
|||
}
|
||||
}
|
||||
} else {
|
||||
tll_foreach(m->workspaces, it) {
|
||||
if (strcoll(it->item.name, ws.name) > 0 ||
|
||||
it->item.name_as_int >= 0)
|
||||
{
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (strcoll(it->item.name, ws.name) > 0 || it->item.name_as_int >= 0) {
|
||||
tll_insert_before(m->workspaces, it, ws);
|
||||
return;
|
||||
}
|
||||
|
@ -157,14 +227,16 @@ workspace_add(struct private *m, struct workspace ws)
|
|||
|
||||
case SORT_DESCENDING:
|
||||
if (ws.name_as_int >= 0) {
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (it->item.name_as_int < ws.name_as_int) {
|
||||
tll_insert_before(m->workspaces, it, ws);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (it->item.name_as_int >= 0)
|
||||
continue;
|
||||
if (strcoll(it->item.name, ws.name) < 0) {
|
||||
|
@ -179,12 +251,13 @@ workspace_add(struct private *m, struct workspace ws)
|
|||
}
|
||||
|
||||
static void
|
||||
workspace_del(struct private *m, const char *name)
|
||||
workspace_del(struct private *m, int id)
|
||||
{
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
struct workspace *ws = &it->item;
|
||||
|
||||
if (strcmp(ws->name, name) != 0)
|
||||
if (ws->id != id)
|
||||
continue;
|
||||
|
||||
workspace_free(ws);
|
||||
|
@ -194,9 +267,22 @@ workspace_del(struct private *m, const char *name)
|
|||
}
|
||||
|
||||
static struct workspace *
|
||||
workspace_lookup(struct private *m, const char *name)
|
||||
workspace_lookup(struct private *m, int id)
|
||||
{
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
struct workspace *ws = &it->item;
|
||||
if (ws->id == id)
|
||||
return ws;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct workspace *
|
||||
workspace_lookup_by_name(struct private *m, const char *name)
|
||||
{
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
struct workspace *ws = &it->item;
|
||||
if (strcmp(ws->name, name) == 0)
|
||||
return ws;
|
||||
|
@ -205,7 +291,7 @@ workspace_lookup(struct private *m, const char *name)
|
|||
}
|
||||
|
||||
static bool
|
||||
handle_get_version_reply(int type, const struct json_object *json, void *_m)
|
||||
handle_get_version_reply(int sock, int type, const struct json_object *json, void *_m)
|
||||
{
|
||||
struct json_object *version;
|
||||
if (!json_object_object_get_ex(json, "human_readable", &version)) {
|
||||
|
@ -218,7 +304,7 @@ handle_get_version_reply(int type, const struct json_object *json, void *_m)
|
|||
}
|
||||
|
||||
static bool
|
||||
handle_subscribe_reply(int type, const struct json_object *json, void *_m)
|
||||
handle_subscribe_reply(int sock, int type, const struct json_object *json, void *_m)
|
||||
{
|
||||
struct json_object *success;
|
||||
if (!json_object_object_get_ex(json, "success", &success)) {
|
||||
|
@ -235,36 +321,88 @@ handle_subscribe_reply(int type, const struct json_object *json, void *_m)
|
|||
}
|
||||
|
||||
static bool
|
||||
handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod)
|
||||
workspace_update_or_add(struct private *m, const struct json_object *ws_json)
|
||||
{
|
||||
struct json_object *_id;
|
||||
if (!json_object_object_get_ex(ws_json, "id", &_id))
|
||||
return false;
|
||||
|
||||
const int id = json_object_get_int(_id);
|
||||
struct workspace *already_exists = workspace_lookup(m, id);
|
||||
|
||||
if (already_exists == NULL) {
|
||||
/*
|
||||
* No workspace with this ID.
|
||||
*
|
||||
* Try looking it up again, but this time using the name. If
|
||||
* we get a match, check if it’s an empty, persistent
|
||||
* workspace, and if so, use it.
|
||||
*
|
||||
* This is necessary, since empty, persistent workspaces don’t
|
||||
* exist in the i3/Sway server, and thus we don’t _have_ an
|
||||
* ID.
|
||||
*/
|
||||
struct json_object *_name;
|
||||
if (json_object_object_get_ex(ws_json, "name", &_name)) {
|
||||
const char *name = json_object_get_string(_name);
|
||||
if (name != NULL) {
|
||||
struct workspace *maybe_persistent = workspace_lookup_by_name(m, name);
|
||||
|
||||
if (maybe_persistent != NULL && maybe_persistent->persistent && maybe_persistent->id < 0) {
|
||||
already_exists = maybe_persistent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (already_exists != NULL) {
|
||||
bool persistent = already_exists->persistent;
|
||||
assert(persistent);
|
||||
|
||||
workspace_free(already_exists);
|
||||
if (!workspace_from_json(ws_json, already_exists))
|
||||
return false;
|
||||
already_exists->persistent = persistent;
|
||||
} else {
|
||||
struct workspace ws;
|
||||
if (!workspace_from_json(ws_json, &ws))
|
||||
return false;
|
||||
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_get_workspaces_reply(int sock, int type, const struct json_object *json, void *_mod)
|
||||
{
|
||||
struct module *mod = _mod;
|
||||
struct private *m = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
workspaces_free(m);
|
||||
workspaces_free(m, false);
|
||||
m->dirty = true;
|
||||
|
||||
size_t count = json_object_array_length(json);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
struct workspace ws = {};
|
||||
if (!workspace_from_json(json_object_array_get_idx(json, i), &ws)) {
|
||||
workspaces_free(m);
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("#%zu: %s", i, m->workspaces.v[i].name);
|
||||
workspace_add(m, ws);
|
||||
if (!workspace_update_or_add(m, json_object_array_get_idx(json, i)))
|
||||
goto err;
|
||||
}
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
|
||||
err:
|
||||
workspaces_free(m, false);
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
||||
handle_workspace_event(int sock, int type, const struct json_object *json, void *_mod)
|
||||
{
|
||||
struct module *mod = _mod;
|
||||
struct private *m = mod->private;
|
||||
|
@ -280,67 +418,59 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
|||
bool is_init = strcmp(change_str, "init") == 0;
|
||||
bool is_empty = strcmp(change_str, "empty") == 0;
|
||||
bool is_focused = strcmp(change_str, "focus") == 0;
|
||||
bool is_rename = strcmp(change_str, "rename") == 0;
|
||||
bool is_move = strcmp(change_str, "move") == 0;
|
||||
bool is_urgent = strcmp(change_str, "urgent") == 0;
|
||||
bool is_reload = strcmp(change_str, "reload") == 0;
|
||||
|
||||
if (is_reload) {
|
||||
LOG_WARN("unimplemented: 'reload' event");
|
||||
return true;
|
||||
}
|
||||
|
||||
struct json_object *current, *_current_name;
|
||||
if (!json_object_object_get_ex(json, "current", ¤t) ||
|
||||
!json_object_object_get_ex(current, "name", &_current_name))
|
||||
{
|
||||
LOG_ERR("workspace event without 'current' and/or 'name' properties");
|
||||
struct json_object *current, *_current_id;
|
||||
if ((!json_object_object_get_ex(json, "current", ¤t)
|
||||
|| !json_object_object_get_ex(current, "id", &_current_id))
|
||||
&& !is_reload) {
|
||||
LOG_ERR("workspace event without 'current' and/or 'id' properties");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *current_name = json_object_get_string(_current_name);
|
||||
int current_id = json_object_get_int(_current_id);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
if (is_init) {
|
||||
struct workspace *already_exists = workspace_lookup(m, current_name);
|
||||
if (already_exists != NULL) {
|
||||
LOG_WARN("workspace 'init' event for already existing workspace: %s", current_name);
|
||||
workspace_free(already_exists);
|
||||
if (!workspace_from_json(current, already_exists))
|
||||
goto err;
|
||||
} else {
|
||||
struct workspace ws;
|
||||
if (!workspace_from_json(current, &ws))
|
||||
goto err;
|
||||
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
if (!workspace_update_or_add(m, current))
|
||||
goto err;
|
||||
}
|
||||
|
||||
else if (is_empty) {
|
||||
assert(workspace_lookup(m, current_name) != NULL);
|
||||
workspace_del(m, current_name);
|
||||
struct workspace *ws = workspace_lookup(m, current_id);
|
||||
assert(ws != NULL);
|
||||
|
||||
if (!ws->persistent)
|
||||
workspace_del(m, current_id);
|
||||
else {
|
||||
workspace_free_persistent(ws);
|
||||
ws->empty = true;
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_focused) {
|
||||
struct json_object *old, *_old_name, *urgent;
|
||||
if (!json_object_object_get_ex(json, "old", &old) ||
|
||||
!json_object_object_get_ex(old, "name", &_old_name) ||
|
||||
!json_object_object_get_ex(current, "urgent", &urgent))
|
||||
{
|
||||
struct json_object *old, *_old_id, *urgent;
|
||||
if (!json_object_object_get_ex(json, "old", &old) || !json_object_object_get_ex(old, "id", &_old_id)
|
||||
|| !json_object_object_get_ex(current, "urgent", &urgent)) {
|
||||
LOG_ERR("workspace 'focused' event without 'old', 'name' and/or 'urgent' property");
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct workspace *w = workspace_lookup(m, current_name);
|
||||
struct workspace *w = workspace_lookup(m, current_id);
|
||||
assert(w != NULL);
|
||||
|
||||
LOG_DBG("w: %s", w->name);
|
||||
|
||||
/* Mark all workspaces on current's output invisible */
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
struct workspace *ws = &it->item;
|
||||
if (strcmp(ws->output, w->output) == 0)
|
||||
if (ws->output != NULL && strcmp(ws->output, w->output) == 0)
|
||||
ws->visible = false;
|
||||
}
|
||||
|
||||
|
@ -349,12 +479,67 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
|||
w->visible = true;
|
||||
|
||||
/* Old workspace is no longer focused */
|
||||
const char *old_name = json_object_get_string(_old_name);
|
||||
struct workspace *old_w = workspace_lookup(m, old_name);
|
||||
int old_id = json_object_get_int(_old_id);
|
||||
struct workspace *old_w = workspace_lookup(m, old_id);
|
||||
if (old_w != NULL)
|
||||
old_w->focused = false;
|
||||
}
|
||||
|
||||
else if (is_rename) {
|
||||
struct workspace *w = workspace_lookup(m, current_id);
|
||||
assert(w != NULL);
|
||||
|
||||
struct json_object *_current_name;
|
||||
if (!json_object_object_get_ex(current, "name", &_current_name)) {
|
||||
LOG_ERR("workspace 'rename' event without 'name' property");
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
free(w->name);
|
||||
w->name = strdup(json_object_get_string(_current_name));
|
||||
w->name_as_int = workspace_name_as_int(w->name);
|
||||
|
||||
/* Re-add the workspace to ensure correct sorting */
|
||||
struct workspace ws = *w;
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (it->item.id == current_id) {
|
||||
tll_remove(m->workspaces, it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
|
||||
else if (is_move) {
|
||||
struct workspace *w = workspace_lookup(m, current_id);
|
||||
|
||||
struct json_object *_current_output;
|
||||
if (!json_object_object_get_ex(current, "output", &_current_output)) {
|
||||
LOG_ERR("workspace 'move' event without 'output' property");
|
||||
mtx_unlock(&mod->lock);
|
||||
return false;
|
||||
}
|
||||
const char *current_output_string = json_object_get_string(_current_output);
|
||||
|
||||
/* Ignore fallback_output ("For when there's no connected outputs") */
|
||||
if (strcmp(current_output_string, "FALLBACK") != 0) {
|
||||
|
||||
assert(w != NULL);
|
||||
free(w->output);
|
||||
w->output = strdup(current_output_string);
|
||||
|
||||
/*
|
||||
* If the moved workspace was focused, schedule a full update because
|
||||
* visibility for other workspaces may have changed.
|
||||
*/
|
||||
if (w->focused) {
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_urgent) {
|
||||
struct json_object *urgent;
|
||||
if (!json_object_object_get_ex(current, "urgent", &urgent)) {
|
||||
|
@ -363,10 +548,20 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
|||
return false;
|
||||
}
|
||||
|
||||
struct workspace *w = workspace_lookup(m, current_name);
|
||||
struct workspace *w = workspace_lookup(m, current_id);
|
||||
w->urgent = json_object_get_boolean(urgent);
|
||||
}
|
||||
|
||||
else if (is_reload) {
|
||||
/* Schedule full update to check if anything was changed
|
||||
* during reload */
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
|
||||
else {
|
||||
LOG_WARN("unimplemented workspace event '%s'", change_str);
|
||||
}
|
||||
|
||||
m->dirty = true;
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
|
@ -377,7 +572,7 @@ err:
|
|||
}
|
||||
|
||||
static bool
|
||||
handle_window_event(int type, const struct json_object *json, void *_mod)
|
||||
handle_window_event(int sock, int type, const struct json_object *json, void *_mod)
|
||||
{
|
||||
struct module *mod = _mod;
|
||||
struct private *m = mod->private;
|
||||
|
@ -399,8 +594,9 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
mtx_lock(&mod->lock);
|
||||
|
||||
struct workspace *ws = NULL;
|
||||
size_t focused = 0;
|
||||
tll_foreach(m->workspaces, it) {
|
||||
__attribute__((unused)) size_t focused = 0;
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
if (it->item.focused) {
|
||||
ws = &it->item;
|
||||
focused++;
|
||||
|
@ -410,6 +606,20 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
assert(focused == 1);
|
||||
assert(ws != NULL);
|
||||
|
||||
struct json_object *container, *id, *name;
|
||||
if (!json_object_object_get_ex(json, "container", &container) || !json_object_object_get_ex(container, "id", &id)
|
||||
|| !json_object_object_get_ex(container, "name", &name)) {
|
||||
mtx_unlock(&mod->lock);
|
||||
LOG_ERR("window event without 'container' with 'id' and 'name'");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((is_close || is_title) && ws->window.id != json_object_get_int(id)) {
|
||||
/* Ignore close event and title changed event if it's not current window */
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_close) {
|
||||
free(ws->window.title);
|
||||
free(ws->window.application);
|
||||
|
@ -421,23 +631,6 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
m->dirty = true;
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
struct json_object *container, *id, *name;
|
||||
if (!json_object_object_get_ex(json, "container", &container) ||
|
||||
!json_object_object_get_ex(container, "id", &id) ||
|
||||
!json_object_object_get_ex(container, "name", &name))
|
||||
{
|
||||
mtx_unlock(&mod->lock);
|
||||
LOG_ERR("window event without 'container' with 'id' and 'name'");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_title && ws->window.id != json_object_get_int(id)) {
|
||||
/* Ignore title changed event if it's not current window */
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
free(ws->window.title);
|
||||
|
@ -458,27 +651,24 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
struct json_object *app_id;
|
||||
struct json_object *pid;
|
||||
|
||||
if (json_object_object_get_ex(container, "app_id", &app_id) &&
|
||||
json_object_get_string(app_id) != NULL)
|
||||
{
|
||||
if (json_object_object_get_ex(container, "app_id", &app_id) && json_object_get_string(app_id) != NULL) {
|
||||
free(ws->window.application);
|
||||
ws->window.application = strdup(json_object_get_string(app_id));
|
||||
LOG_DBG("application: \"%s\", via 'app_id'", ws->window.application);
|
||||
}
|
||||
|
||||
/* If PID has changed, update application name from /proc/<pid>/comm */
|
||||
else if (json_object_object_get_ex(container, "pid", &pid) &&
|
||||
ws->window.pid != json_object_get_int(pid))
|
||||
{
|
||||
else if (json_object_object_get_ex(container, "pid", &pid) && ws->window.pid != json_object_get_int(pid)) {
|
||||
ws->window.pid = json_object_get_int(pid);
|
||||
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid);
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
/* Application may simply have terminated */
|
||||
free(ws->window.application); ws->window.application = NULL;
|
||||
free(ws->window.application);
|
||||
ws->window.application = NULL;
|
||||
ws->window.pid = -1;
|
||||
|
||||
m->dirty = true;
|
||||
|
@ -503,7 +693,7 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
}
|
||||
|
||||
static bool
|
||||
handle_mode_event(int type, const struct json_object *json, void *_mod)
|
||||
handle_mode_event(int sock, int type, const struct json_object *json, void *_mod)
|
||||
{
|
||||
struct module *mod = _mod;
|
||||
struct private *m = mod->private;
|
||||
|
@ -545,7 +735,7 @@ run(struct module *mod)
|
|||
if (!i3_get_socket_address(&addr))
|
||||
return 1;
|
||||
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (sock == -1) {
|
||||
LOG_ERRNO("failed to create UNIX socket");
|
||||
return 1;
|
||||
|
@ -558,6 +748,27 @@ run(struct module *mod)
|
|||
return 1;
|
||||
}
|
||||
|
||||
struct private *m = mod->private;
|
||||
for (size_t i = 0; i < m->persistent_count; i++) {
|
||||
const char *name_as_string = m->persistent_workspaces[i];
|
||||
|
||||
int name_as_int = workspace_name_as_int(name_as_string);
|
||||
if (m->strip_workspace_numbers) {
|
||||
const char *colon = strchr(name_as_string, ':');
|
||||
if (colon != NULL)
|
||||
name_as_string = colon++;
|
||||
}
|
||||
|
||||
struct workspace ws = {
|
||||
.id = -1,
|
||||
.name = strdup(name_as_string),
|
||||
.name_as_int = name_as_int,
|
||||
.persistent = true,
|
||||
.empty = true,
|
||||
};
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL);
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\", \"mode\"]");
|
||||
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
|
@ -589,7 +800,11 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
free(m->ws_content.v);
|
||||
workspaces_free(m);
|
||||
workspaces_free(m, true);
|
||||
|
||||
for (size_t i = 0; i < m->persistent_count; i++)
|
||||
free(m->persistent_workspaces[i]);
|
||||
free(m->persistent_workspaces);
|
||||
|
||||
free(m->mode);
|
||||
free(m);
|
||||
|
@ -609,7 +824,7 @@ ws_content_for_name(struct private *m, const char *name)
|
|||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "i3/sway";
|
||||
}
|
||||
|
@ -625,30 +840,47 @@ content(struct module *mod)
|
|||
struct exposable *particles[tll_length(m->workspaces) + 1];
|
||||
struct exposable *current = NULL;
|
||||
|
||||
tll_foreach(m->workspaces, it) {
|
||||
tll_foreach(m->workspaces, it)
|
||||
{
|
||||
struct workspace *ws = &it->item;
|
||||
const struct ws_content *template = NULL;
|
||||
|
||||
/* Lookup content template for workspace. Fall back to default
|
||||
* template if this workspace doesn't have a specific
|
||||
* template */
|
||||
if (ws->name == NULL) {
|
||||
LOG_ERR("%d %d", ws->name_as_int, ws->id);
|
||||
}
|
||||
template = ws_content_for_name(m, ws->name);
|
||||
if (template == NULL) {
|
||||
LOG_DBG("no ws template for %s, using default template", ws->name);
|
||||
template = ws_content_for_name(m, "");
|
||||
}
|
||||
|
||||
const char *state =
|
||||
ws->urgent ? "urgent" :
|
||||
ws->visible ? ws->focused ? "focused" : "unfocused" :
|
||||
"invisible";
|
||||
const char *state = ws->urgent ? "urgent" : ws->visible ? ws->focused ? "focused" : "unfocused" : "invisible";
|
||||
|
||||
LOG_DBG("name=%s (name-as-int=%d): visible=%s, focused=%s, urgent=%s, empty=%s, state=%s, "
|
||||
"application=%s, title=%s, mode=%s",
|
||||
ws->name, ws->name_as_int, ws->visible ? "yes" : "no", ws->focused ? "yes" : "no",
|
||||
ws->urgent ? "yes" : "no", ws->empty ? "yes" : "no", state, ws->window.application, ws->window.title,
|
||||
m->mode);
|
||||
|
||||
const char *name = ws->name;
|
||||
|
||||
if (m->strip_workspace_numbers) {
|
||||
const char *colon = strchr(name, ':');
|
||||
if (colon != NULL)
|
||||
name = colon + 1;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_string(mod, "name", ws->name),
|
||||
tag_new_string(mod, "name", name),
|
||||
tag_new_string(mod, "output", ws->output),
|
||||
tag_new_bool(mod, "visible", ws->visible),
|
||||
tag_new_bool(mod, "focused", ws->focused),
|
||||
tag_new_bool(mod, "urgent", ws->urgent),
|
||||
tag_new_bool(mod, "empty", ws->empty),
|
||||
tag_new_string(mod, "state", state),
|
||||
|
||||
tag_new_string(mod, "application", ws->window.application),
|
||||
|
@ -656,7 +888,7 @@ content(struct module *mod)
|
|||
|
||||
tag_new_string(mod, "mode", m->mode),
|
||||
},
|
||||
.count = 8,
|
||||
.count = 10,
|
||||
};
|
||||
|
||||
if (ws->focused) {
|
||||
|
@ -666,12 +898,9 @@ content(struct module *mod)
|
|||
}
|
||||
|
||||
if (template == NULL) {
|
||||
LOG_WARN(
|
||||
"no ws template for %s, and no default template available",
|
||||
ws->name);
|
||||
LOG_WARN("no ws template for %s, and no default template available", ws->name);
|
||||
} else {
|
||||
particles[particle_count++] = template->content->instantiate(
|
||||
template->content, &tags);
|
||||
particles[particle_count++] = template->content->instantiate(template->content, &tags);
|
||||
}
|
||||
|
||||
tag_set_destroy(&tags);
|
||||
|
@ -681,8 +910,7 @@ content(struct module *mod)
|
|||
particles[particle_count++] = current;
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
return dynlist_exposable_new(
|
||||
particles, particle_count, m->left_spacing, m->right_spacing);
|
||||
return dynlist_exposable_new(particles, particle_count, m->left_spacing, m->right_spacing);
|
||||
}
|
||||
|
||||
/* Maps workspace name to a content particle. */
|
||||
|
@ -692,8 +920,9 @@ struct i3_workspaces {
|
|||
};
|
||||
|
||||
static struct module *
|
||||
i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
|
||||
int left_spacing, int right_spacing, enum sort_mode sort_mode)
|
||||
i3_new(struct i3_workspaces workspaces[], size_t workspace_count, int left_spacing, int right_spacing,
|
||||
enum sort_mode sort_mode, size_t persistent_count, const char *persistent_workspaces[static persistent_count],
|
||||
bool strip_workspace_numbers)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
|
||||
|
@ -709,8 +938,15 @@ i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
|
|||
m->ws_content.v[i].content = workspaces[i].content;
|
||||
}
|
||||
|
||||
m->strip_workspace_numbers = strip_workspace_numbers;
|
||||
m->sort_mode = sort_mode;
|
||||
|
||||
m->persistent_count = persistent_count;
|
||||
m->persistent_workspaces = calloc(persistent_count, sizeof(m->persistent_workspaces[0]));
|
||||
|
||||
for (size_t i = 0; i < persistent_count; i++)
|
||||
m->persistent_workspaces[i] = strdup(persistent_workspaces[i]);
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = m;
|
||||
mod->run = &run;
|
||||
|
@ -728,50 +964,55 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *left_spacing = yml_get_value(node, "left-spacing");
|
||||
const struct yml_node *right_spacing = yml_get_value(node, "right-spacing");
|
||||
const struct yml_node *sort = yml_get_value(node, "sort");
|
||||
const struct yml_node *persistent = yml_get_value(node, "persistent");
|
||||
const struct yml_node *strip_workspace_number = yml_get_value(node, "strip-workspace-numbers");
|
||||
|
||||
int left = spacing != NULL ? yml_value_as_int(spacing) :
|
||||
left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
|
||||
int right = spacing != NULL ? yml_value_as_int(spacing) :
|
||||
right_spacing != NULL ? yml_value_as_int(right_spacing) : 0;
|
||||
int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
|
||||
int right = spacing != NULL ? yml_value_as_int(spacing)
|
||||
: right_spacing != NULL ? yml_value_as_int(right_spacing)
|
||||
: 0;
|
||||
|
||||
const char *sort_value = sort != NULL ? yml_value_as_string(sort) : NULL;
|
||||
enum sort_mode sort_mode =
|
||||
sort_value == NULL ? SORT_NONE :
|
||||
strcmp(sort_value, "none") == 0 ? SORT_NONE :
|
||||
strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING : SORT_DESCENDING;
|
||||
enum sort_mode sort_mode = sort_value == NULL ? SORT_NONE
|
||||
: strcmp(sort_value, "none") == 0 ? SORT_NONE
|
||||
: strcmp(sort_value, "native") == 0 ? SORT_NATIVE
|
||||
: strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING
|
||||
: SORT_DESCENDING;
|
||||
|
||||
const size_t persistent_count = persistent != NULL ? yml_list_length(persistent) : 0;
|
||||
const char *persistent_workspaces[persistent_count];
|
||||
|
||||
if (persistent != NULL) {
|
||||
size_t idx = 0;
|
||||
for (struct yml_list_iter it = yml_list_iter(persistent); it.node != NULL; yml_list_next(&it), idx++) {
|
||||
persistent_workspaces[idx] = yml_value_as_string(it.node);
|
||||
}
|
||||
}
|
||||
|
||||
struct i3_workspaces workspaces[yml_dict_length(c)];
|
||||
|
||||
size_t idx = 0;
|
||||
for (struct yml_dict_iter it = yml_dict_iter(c);
|
||||
it.key != NULL;
|
||||
yml_dict_next(&it), idx++)
|
||||
{
|
||||
for (struct yml_dict_iter it = yml_dict_iter(c); it.key != NULL; yml_dict_next(&it), idx++) {
|
||||
workspaces[idx].name = yml_value_as_string(it.key);
|
||||
workspaces[idx].content = conf_to_particle(it.value, inherited);
|
||||
}
|
||||
|
||||
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode);
|
||||
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode, persistent_count, persistent_workspaces,
|
||||
(strip_workspace_number != NULL ? yml_value_as_bool(strip_workspace_number) : false));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_content(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_dict(node)) {
|
||||
LOG_ERR(
|
||||
"%s: must be a dictionary of workspace-name: particle mappings",
|
||||
conf_err_prefix(chain, node));
|
||||
LOG_ERR("%s: must be a dictionary of workspace-name: particle mappings", conf_err_prefix(chain, node));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (struct yml_dict_iter it = yml_dict_iter(node);
|
||||
it.key != NULL;
|
||||
yml_dict_next(&it))
|
||||
{
|
||||
for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) {
|
||||
const char *key = yml_value_as_string(it.key);
|
||||
if (key == NULL) {
|
||||
LOG_ERR("%s: key must be a string (a i3 workspace name)",
|
||||
conf_err_prefix(chain, it.key));
|
||||
LOG_ERR("%s: key must be a string (a i3 workspace name)", conf_err_prefix(chain, it.key));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -787,18 +1028,25 @@ verify_content(keychain_t *chain, const struct yml_node *node)
|
|||
static bool
|
||||
verify_sort(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_enum(
|
||||
chain, node, (const char *[]){"none", "ascending", "descending"}, 3);
|
||||
return conf_verify_enum(chain, node, (const char *[]){"none", "native", "ascending", "descending"}, 4);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_persistent(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
return conf_verify_list(chain, node, &conf_verify_string);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
{"sort", false, &verify_sort},
|
||||
{"persistent", false, &verify_persistent},
|
||||
{"strip-workspace-numbers", false, &conf_verify_bool},
|
||||
{"content", true, &verify_content},
|
||||
{"anchors", false, NULL},
|
||||
{NULL, false, NULL},
|
||||
|
@ -813,5 +1061,5 @@ const struct module_iface module_i3_iface = {
|
|||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_i3_iface"))) ;
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_i3_iface")));
|
||||
#endif
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../module.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct particle *label;
|
||||
};
|
||||
struct private { struct particle *label; };
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
|
@ -22,7 +20,7 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "label";
|
||||
}
|
||||
|
|
200
modules/mem.c
Normal file
200
modules/mem.c
Normal file
|
@ -0,0 +1,200 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_MODULE "mem"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
static const long min_poll_interval = 250;
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
uint16_t interval;
|
||||
uint64_t mem_free;
|
||||
uint64_t mem_total;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->label->destroy(m->label);
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "mem";
|
||||
}
|
||||
|
||||
static bool
|
||||
get_mem_stats(uint64_t *mem_free, uint64_t *mem_total)
|
||||
{
|
||||
bool mem_total_found = false;
|
||||
bool mem_free_found = false;
|
||||
|
||||
FILE *fp = NULL;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t read = 0;
|
||||
|
||||
fp = fopen("/proc/meminfo", "re");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/meminfo");
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1) {
|
||||
if (strncmp(line, "MemTotal:", sizeof("MemTotal:") - 1) == 0) {
|
||||
read = sscanf(line + sizeof("MemTotal:") - 1, "%" SCNu64, mem_total);
|
||||
mem_total_found = (read == 1);
|
||||
}
|
||||
if (strncmp(line, "MemAvailable:", sizeof("MemAvailable:") - 1) == 0) {
|
||||
read = sscanf(line + sizeof("MemAvailable:"), "%" SCNu64, mem_free);
|
||||
mem_free_found = (read == 1);
|
||||
}
|
||||
}
|
||||
free(line);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return mem_free_found && mem_total_found;
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *p = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
const uint64_t mem_free = p->mem_free;
|
||||
const uint64_t mem_total = p->mem_total;
|
||||
const uint64_t mem_used = mem_total - mem_free;
|
||||
|
||||
double percent_used = ((double)mem_used * 100) / (mem_total + 1);
|
||||
double percent_free = ((double)mem_free * 100) / (mem_total + 1);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){tag_new_int(mod, "free", mem_free * 1024), tag_new_int(mod, "used", mem_used * 1024),
|
||||
tag_new_int(mod, "total", mem_total * 1024),
|
||||
tag_new_int_range(mod, "percent_free", round(percent_free), 0, 100),
|
||||
tag_new_int_range(mod, "percent_used", round(percent_used), 0, 100)},
|
||||
.count = 5,
|
||||
};
|
||||
|
||||
struct exposable *exposable = p->label->instantiate(p->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
mtx_unlock(&mod->lock);
|
||||
return exposable;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
struct private *p = mod->private;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
|
||||
int res = poll(fds, 1, p->interval);
|
||||
if (res < 0) {
|
||||
if (EINTR == errno) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_ERRNO("unable to poll abort fd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
p->mem_free = 0;
|
||||
p->mem_total = 0;
|
||||
if (!get_mem_stats(&p->mem_free, &p->mem_total)) {
|
||||
LOG_ERR("unable to retrieve the memory stats");
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
mem_new(uint16_t interval, struct particle *label)
|
||||
{
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->label = label;
|
||||
p->interval = interval;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = p;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *interval = yml_get_value(node, "poll-interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return mem_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < min_poll_interval) {
|
||||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"poll-interval", false, &conf_verify_poll_interval},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_mem_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_mem_iface")));
|
||||
#endif
|
|
@ -2,35 +2,157 @@ module_sdk = declare_dependency(dependencies: [pixman, threads, tllist, fcft])
|
|||
|
||||
modules = []
|
||||
|
||||
alsa = dependency('alsa')
|
||||
udev = dependency('libudev')
|
||||
json = dependency('json-c')
|
||||
mpd = dependency('libmpdclient')
|
||||
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
|
||||
# Optional deps
|
||||
alsa = dependency('alsa', required: get_option('plugin-alsa'))
|
||||
plugin_alsa_enabled = alsa.found()
|
||||
|
||||
udev_backlight = dependency('libudev', required: get_option('plugin-backlight'))
|
||||
plugin_backlight_enabled = udev_backlight.found()
|
||||
|
||||
udev_battery = dependency('libudev', required: get_option('plugin-battery'))
|
||||
plugin_battery_enabled = udev_battery.found()
|
||||
|
||||
plugin_clock_enabled = get_option('plugin-clock').allowed()
|
||||
plugin_cpu_enabled = get_option('plugin-cpu').allowed()
|
||||
plugin_disk_io_enabled = get_option('plugin-disk-io').allowed()
|
||||
plugin_dwl_enabled = get_option('plugin-dwl').allowed()
|
||||
plugin_foreign_toplevel_enabled = backend_wayland and get_option('plugin-foreign-toplevel').allowed()
|
||||
plugin_mem_enabled = get_option('plugin-mem').allowed()
|
||||
|
||||
mpd = dependency('libmpdclient', required: get_option('plugin-mpd'))
|
||||
plugin_mpd_enabled = mpd.found()
|
||||
|
||||
# DBus dependency. Used by 'mpris'
|
||||
sdbus_library = dependency('libsystemd', 'libelogind', 'basu', required: get_option('plugin-mpris'))
|
||||
plugin_mpris_enabled = sdbus_library.found()
|
||||
|
||||
json_i3 = dependency('json-c', required: get_option('plugin-i3'))
|
||||
plugin_i3_enabled = json_i3.found()
|
||||
|
||||
plugin_label_enabled = get_option('plugin-label').allowed()
|
||||
plugin_network_enabled = get_option('plugin-network').allowed()
|
||||
|
||||
pipewire = dependency('libpipewire-0.3', required: get_option('plugin-pipewire'))
|
||||
json_pipewire = dependency('json-c', required: get_option('plugin-pipewire'))
|
||||
plugin_pipewire_enabled = pipewire.found() and json_pipewire.found()
|
||||
|
||||
pulse = dependency('libpulse', required: get_option('plugin-pulse'))
|
||||
plugin_pulse_enabled = pulse.found()
|
||||
|
||||
udev_removables = dependency('libudev', required: get_option('plugin-removables'))
|
||||
plugin_removables_enabled = udev_removables.found()
|
||||
|
||||
plugin_river_enabled = backend_wayland and get_option('plugin-river').allowed()
|
||||
|
||||
plugin_script_enabled = get_option('plugin-script').allowed()
|
||||
|
||||
json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb'))
|
||||
plugin_sway_xkb_enabled = json_sway_xkb.found()
|
||||
|
||||
json_niri_language = dependency('json-c', required: get_option('plugin-niri-language'))
|
||||
plugin_niri_language_enabled = json_niri_language.found()
|
||||
|
||||
json_niri_workspaces = dependency('json-c', required: get_option('plugin-niri-workspaces'))
|
||||
plugin_niri_workspaces_enabled = json_niri_workspaces.found()
|
||||
|
||||
xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb'))
|
||||
plugin_xkb_enabled = backend_x11 and xcb_xkb.found()
|
||||
|
||||
plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed()
|
||||
|
||||
# Module name -> (source-list, dep-list)
|
||||
mod_data = {
|
||||
'alsa': [[], [m, alsa]],
|
||||
'backlight': [[], [m, udev]],
|
||||
'battery': [[], [udev]],
|
||||
'clock': [[], []],
|
||||
'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||
'label': [[], []],
|
||||
'mpd': [[], [mpd]],
|
||||
'network': [[], []],
|
||||
'removables': [[], [dynlist, udev]],
|
||||
'script': [[], []],
|
||||
'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||
}
|
||||
mod_data = {}
|
||||
|
||||
if backend_x11
|
||||
mod_data += {
|
||||
'xkb': [[], [xcb_stuff, xcb_xkb]],
|
||||
'xwindow': [[], [xcb_stuff]],
|
||||
}
|
||||
if plugin_alsa_enabled
|
||||
mod_data += {'alsa': [[], [m, alsa]]}
|
||||
endif
|
||||
|
||||
if backend_wayland
|
||||
if plugin_backlight_enabled
|
||||
mod_data += {'backlight': [[], [m, udev_backlight]]}
|
||||
endif
|
||||
|
||||
if plugin_battery_enabled
|
||||
mod_data += {'battery': [[], [udev_battery]]}
|
||||
endif
|
||||
|
||||
if plugin_clock_enabled
|
||||
mod_data += {'clock': [[], []]}
|
||||
endif
|
||||
|
||||
if plugin_cpu_enabled
|
||||
mod_data += {'cpu': [[], [m, dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_disk_io_enabled
|
||||
mod_data += {'disk-io': [[], [dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_dwl_enabled
|
||||
mod_data += {'dwl': [[], [dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_mem_enabled
|
||||
mod_data += {'mem': [[], [m]]}
|
||||
endif
|
||||
|
||||
if plugin_mpd_enabled
|
||||
mod_data += {'mpd': [[], [mpd]]}
|
||||
endif
|
||||
|
||||
if plugin_mpris_enabled
|
||||
sdbus = declare_dependency(compile_args: ['-DHAVE_' + sdbus_library.name().to_upper()], dependencies:[sdbus_library])
|
||||
mod_data += {'mpris': [[], [sdbus]]}
|
||||
endif
|
||||
|
||||
if plugin_i3_enabled
|
||||
mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]}
|
||||
endif
|
||||
|
||||
if plugin_label_enabled
|
||||
mod_data += {'label': [[], []]}
|
||||
endif
|
||||
|
||||
if plugin_network_enabled
|
||||
mod_data += {'network': [[], [dynlist]]}
|
||||
endif
|
||||
|
||||
if plugin_pipewire_enabled
|
||||
mod_data += {'pipewire': [[], [m, pipewire, dynlist, json_pipewire]]}
|
||||
endif
|
||||
|
||||
if plugin_pulse_enabled
|
||||
mod_data += {'pulse': [[], [m, pulse]]}
|
||||
endif
|
||||
|
||||
if plugin_removables_enabled
|
||||
mod_data += {'removables': [[], [dynlist, udev_removables]]}
|
||||
endif
|
||||
|
||||
if plugin_script_enabled
|
||||
mod_data += {'script': [[], []]}
|
||||
endif
|
||||
|
||||
if plugin_sway_xkb_enabled
|
||||
mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]}
|
||||
endif
|
||||
|
||||
if plugin_niri_language_enabled
|
||||
mod_data += {'niri-language': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_language]]}
|
||||
endif
|
||||
|
||||
if plugin_niri_workspaces_enabled
|
||||
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
|
||||
endif
|
||||
|
||||
if plugin_xkb_enabled
|
||||
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
|
||||
endif
|
||||
|
||||
if plugin_xwindow_enabled
|
||||
mod_data += {'xwindow': [[], [xcb_stuff]]}
|
||||
endif
|
||||
|
||||
if plugin_river_enabled
|
||||
river_proto_headers = []
|
||||
river_proto_src = []
|
||||
|
||||
|
@ -49,9 +171,29 @@ if backend_wayland
|
|||
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||
endforeach
|
||||
|
||||
mod_data += {
|
||||
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]],
|
||||
}
|
||||
mod_data += {'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist, wayland_client]]}
|
||||
endif
|
||||
|
||||
if plugin_foreign_toplevel_enabled
|
||||
ftop_proto_headers = []
|
||||
ftop_proto_src = []
|
||||
|
||||
foreach prot : ['../external/wlr-foreign-toplevel-management-unstable-v1.xml']
|
||||
|
||||
ftop_proto_headers += custom_target(
|
||||
prot.underscorify() + '-client-header',
|
||||
output: '@BASENAME@.h',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'])
|
||||
|
||||
ftop_proto_src += custom_target(
|
||||
prot.underscorify() + '-private-code',
|
||||
output: '@BASENAME@.c',
|
||||
input: prot,
|
||||
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||
endforeach
|
||||
|
||||
mod_data += {'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [m, dynlist, wayland_client]]}
|
||||
endif
|
||||
|
||||
foreach mod, data : mod_data
|
||||
|
|
194
modules/mpd.c
194
modules/mpd.c
|
@ -1,33 +1,34 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <libgen.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <mpd/client.h>
|
||||
|
||||
#define LOG_MODULE "mpd"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../bar/bar.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private {
|
||||
struct private
|
||||
{
|
||||
char *host;
|
||||
uint16_t port;
|
||||
struct particle *label;
|
||||
|
@ -38,10 +39,12 @@ struct private {
|
|||
bool repeat;
|
||||
bool random;
|
||||
bool consume;
|
||||
int volume;
|
||||
bool single;
|
||||
int volume;
|
||||
char *album;
|
||||
char *artist;
|
||||
char *title;
|
||||
char *file;
|
||||
|
||||
struct {
|
||||
uint64_t value;
|
||||
|
@ -59,11 +62,9 @@ destroy(struct module *mod)
|
|||
struct private *m = mod->private;
|
||||
if (m->refresh_thread_id != 0) {
|
||||
assert(m->refresh_abort_fd != -1);
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
LOG_ERRNO("failed to signal abort to refresher thread");
|
||||
} else{
|
||||
} else {
|
||||
int res;
|
||||
thrd_join(m->refresh_thread_id, &res);
|
||||
}
|
||||
|
@ -75,6 +76,7 @@ destroy(struct module *mod)
|
|||
free(m->album);
|
||||
free(m->artist);
|
||||
free(m->title);
|
||||
free(m->file);
|
||||
assert(m->conn == NULL);
|
||||
|
||||
m->label->destroy(m->label);
|
||||
|
@ -84,7 +86,7 @@ destroy(struct module *mod)
|
|||
}
|
||||
|
||||
static const char *
|
||||
description(struct module *mod)
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "mpd";
|
||||
}
|
||||
|
@ -130,12 +132,11 @@ content(struct module *mod)
|
|||
if (m->state == MPD_STATE_PLAY) {
|
||||
elapsed += timespec_diff_milli_seconds(&now, &m->elapsed.when);
|
||||
if (elapsed > m->duration) {
|
||||
LOG_DBG(
|
||||
"dynamic update of elapsed overflowed: "
|
||||
"elapsed=%"PRIu64", duration=%"PRIu64, elapsed, m->duration);
|
||||
LOG_DBG("dynamic update of elapsed overflowed: "
|
||||
"elapsed=%" PRIu64 ", duration=%" PRIu64,
|
||||
elapsed, m->duration);
|
||||
elapsed = m->duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unsigned elapsed_secs = elapsed / 1000;
|
||||
|
@ -152,16 +153,23 @@ content(struct module *mod)
|
|||
state_str = "offline";
|
||||
else {
|
||||
switch (m->state) {
|
||||
case MPD_STATE_UNKNOWN: state_str = "unknown"; break;
|
||||
case MPD_STATE_STOP: state_str = "stopped"; break;
|
||||
case MPD_STATE_PAUSE: state_str = "paused"; break;
|
||||
case MPD_STATE_PLAY: state_str = "playing"; break;
|
||||
case MPD_STATE_UNKNOWN:
|
||||
state_str = "unknown";
|
||||
break;
|
||||
case MPD_STATE_STOP:
|
||||
state_str = "stopped";
|
||||
break;
|
||||
case MPD_STATE_PAUSE:
|
||||
state_str = "paused";
|
||||
break;
|
||||
case MPD_STATE_PLAY:
|
||||
state_str = "playing";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tell particle to real-time track? */
|
||||
enum tag_realtime_unit realtime = m->state == MPD_STATE_PLAY
|
||||
? TAG_REALTIME_MSECS : TAG_REALTIME_NONE;
|
||||
enum tag_realtime_unit realtime = m->state == MPD_STATE_PLAY ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE;
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
|
@ -169,17 +177,19 @@ content(struct module *mod)
|
|||
tag_new_bool(mod, "repeat", m->repeat),
|
||||
tag_new_bool(mod, "random", m->random),
|
||||
tag_new_bool(mod, "consume", m->consume),
|
||||
tag_new_bool(mod, "single", m->single),
|
||||
tag_new_int_range(mod, "volume", m->volume, 0, 100),
|
||||
tag_new_string(mod, "album", m->album),
|
||||
tag_new_string(mod, "artist", m->artist),
|
||||
tag_new_string(mod, "title", m->title),
|
||||
tag_new_string(mod, "file", m->file),
|
||||
tag_new_string(mod, "pos", pos),
|
||||
tag_new_string(mod, "end", end),
|
||||
tag_new_int(mod, "duration", m->duration),
|
||||
tag_new_int_realtime(
|
||||
mod, "elapsed", elapsed, 0, m->duration, realtime),
|
||||
},
|
||||
.count = 12,
|
||||
.count = 14,
|
||||
};
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
@ -223,7 +233,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
struct stat st;
|
||||
if (stat(m->host, &st) == 0 && S_ISSOCK(st.st_mode)) {
|
||||
|
||||
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
|
||||
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||
strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1);
|
||||
|
@ -234,8 +244,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
LOG_DBG("%s: already exists, and is connectable", m->host);
|
||||
have_mpd_socket = true;
|
||||
} else {
|
||||
LOG_DBG("%s: already exists, but isn't connectable: %s",
|
||||
m->host, strerror(errno));
|
||||
LOG_DBG("%s: already exists, but isn't connectable: %s", m->host, strerror(errno));
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
@ -246,12 +255,15 @@ wait_for_socket_create(const struct module *mod)
|
|||
|
||||
bool ret = false;
|
||||
while (!have_mpd_socket) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = fd, .events = POLLIN}
|
||||
};
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}};
|
||||
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = true;
|
||||
|
@ -263,7 +275,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
char buf[1024];
|
||||
ssize_t len = read(fd, buf, sizeof(buf));
|
||||
|
||||
for (const char *ptr = buf; ptr < buf + len; ) {
|
||||
for (const char *ptr = buf; ptr < buf + len;) {
|
||||
const struct inotify_event *e = (const struct inotify_event *)ptr;
|
||||
LOG_DBG("inotify: CREATED: %s/%.*s", directory, e->len, e->name);
|
||||
|
||||
|
@ -273,7 +285,7 @@ wait_for_socket_create(const struct module *mod)
|
|||
break;
|
||||
}
|
||||
|
||||
ptr += sizeof(*e) + e->len;
|
||||
ptr += sizeof(*e) + e->len;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,8 +308,7 @@ connect_to_mpd(const struct module *mod)
|
|||
|
||||
enum mpd_error merr = mpd_connection_get_error(conn);
|
||||
if (merr != MPD_ERROR_SUCCESS) {
|
||||
LOG_WARN("failed to connect to MPD: %s",
|
||||
mpd_connection_get_error_message(conn));
|
||||
LOG_WARN("failed to connect to MPD: %s", mpd_connection_get_error_message(conn));
|
||||
mpd_connection_free(conn);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -315,8 +326,7 @@ update_status(struct module *mod)
|
|||
|
||||
struct mpd_status *status = mpd_run_status(m->conn);
|
||||
if (status == NULL) {
|
||||
LOG_ERR("failed to get status: %s",
|
||||
mpd_connection_get_error_message(m->conn));
|
||||
LOG_ERR("failed to get status: %s", mpd_connection_get_error_message(m->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -328,6 +338,7 @@ update_status(struct module *mod)
|
|||
m->repeat = mpd_status_get_repeat(status);
|
||||
m->random = mpd_status_get_random(status);
|
||||
m->consume = mpd_status_get_consume(status);
|
||||
m->single = mpd_status_get_single_state(status) == MPD_SINGLE_ONESHOT;
|
||||
m->volume = mpd_status_get_volume(status);
|
||||
m->duration = mpd_status_get_total_time(status) * 1000;
|
||||
m->elapsed.value = mpd_status_get_elapsed_ms(status);
|
||||
|
@ -338,30 +349,37 @@ update_status(struct module *mod)
|
|||
|
||||
struct mpd_song *song = mpd_run_current_song(m->conn);
|
||||
if (song == NULL && mpd_connection_get_error(m->conn) != MPD_ERROR_SUCCESS) {
|
||||
LOG_ERR("failed to get current song: %s",
|
||||
mpd_connection_get_error_message(m->conn));
|
||||
LOG_ERR("failed to get current song: %s", mpd_connection_get_error_message(m->conn));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (song == NULL) {
|
||||
mtx_lock(&mod->lock);
|
||||
free(m->album); m->album = NULL;
|
||||
free(m->artist); m->artist = NULL;
|
||||
free(m->title); m->title = NULL;
|
||||
free(m->album);
|
||||
m->album = NULL;
|
||||
free(m->artist);
|
||||
m->artist = NULL;
|
||||
free(m->title);
|
||||
m->title = NULL;
|
||||
free(m->file);
|
||||
m->file = NULL;
|
||||
mtx_unlock(&mod->lock);
|
||||
} else {
|
||||
const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
|
||||
const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
|
||||
const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
|
||||
const char *file = mpd_song_get_uri(song);
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
free(m->album);
|
||||
free(m->artist);
|
||||
free(m->title);
|
||||
free(m->file);
|
||||
|
||||
m->album = album != NULL ? strdup(album) : NULL;
|
||||
m->artist = artist != NULL ? strdup(artist) : NULL;
|
||||
m->title = title != NULL ? strdup(title) : NULL;
|
||||
m->file = file != NULL ? strdup(file) : NULL;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
mpd_song_free(song);
|
||||
|
@ -377,8 +395,9 @@ run(struct module *mod)
|
|||
struct private *m = mod->private;
|
||||
|
||||
bool aborted = false;
|
||||
int ret = 0;
|
||||
|
||||
while (!aborted) {
|
||||
while (!aborted && ret == 0) {
|
||||
|
||||
if (m->conn != NULL) {
|
||||
mpd_connection_free(m->conn);
|
||||
|
@ -387,16 +406,21 @@ run(struct module *mod)
|
|||
|
||||
/* Reset state */
|
||||
mtx_lock(&mod->lock);
|
||||
free(m->album); m->album = NULL;
|
||||
free(m->artist); m->artist = NULL;
|
||||
free(m->title); m->title = NULL;
|
||||
free(m->album);
|
||||
m->album = NULL;
|
||||
free(m->artist);
|
||||
m->artist = NULL;
|
||||
free(m->title);
|
||||
m->title = NULL;
|
||||
free(m->file);
|
||||
m->file = NULL;
|
||||
m->state = MPD_STATE_UNKNOWN;
|
||||
m->elapsed.value = m->duration = 0;
|
||||
m->elapsed.when.tv_sec = m->elapsed.when.tv_nsec = 0;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
/* Keep trying to connect, until we succeed */
|
||||
while (!aborted) {
|
||||
while (!aborted && ret == 0) {
|
||||
if (m->port == 0) {
|
||||
/* Use inotify to watch for socket creation */
|
||||
aborted = wait_for_socket_create(mod);
|
||||
|
@ -414,16 +438,33 @@ run(struct module *mod)
|
|||
* host), wait for a while until we try to re-connect
|
||||
* again.
|
||||
*/
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
int res = poll(fds, 1, 10 * 1000);
|
||||
while (!aborted) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 2 * 1000);
|
||||
|
||||
if (res < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
else if (res == 1) {
|
||||
assert(fds[0].revents & POLLIN);
|
||||
aborted = true;
|
||||
}
|
||||
|
||||
if (res == 1) {
|
||||
assert(fds[0].revents & POLLIN);
|
||||
aborted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (aborted)
|
||||
if (aborted || ret != 0)
|
||||
break;
|
||||
|
||||
/* Initial state (after establishing a connection) */
|
||||
|
@ -441,12 +482,18 @@ run(struct module *mod)
|
|||
};
|
||||
|
||||
if (!mpd_send_idle(m->conn)) {
|
||||
LOG_ERR("failed to send IDLE command: %s",
|
||||
mpd_connection_get_error_message(m->conn));
|
||||
LOG_ERR("failed to send IDLE command: %s", mpd_connection_get_error_message(m->conn));
|
||||
break;
|
||||
}
|
||||
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
aborted = true;
|
||||
|
@ -459,8 +506,7 @@ run(struct module *mod)
|
|||
}
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
enum mpd_idle idle __attribute__ ((unused)) =
|
||||
mpd_recv_idle(m->conn, true);
|
||||
enum mpd_idle idle __attribute__((unused)) = mpd_recv_idle(m->conn, true);
|
||||
|
||||
LOG_DBG("IDLE mask: %d", idle);
|
||||
|
||||
|
@ -477,7 +523,7 @@ run(struct module *mod)
|
|||
m->conn = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return aborted ? 0 : ret;
|
||||
}
|
||||
|
||||
struct refresh_context {
|
||||
|
@ -532,9 +578,7 @@ refresh_in(struct module *mod, long milli_seconds)
|
|||
|
||||
/* Signal abort to thread */
|
||||
assert(m->refresh_abort_fd != -1);
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
||||
LOG_ERRNO("failed to signal abort to refresher thread");
|
||||
return false;
|
||||
}
|
||||
|
@ -574,7 +618,7 @@ refresh_in(struct module *mod, long milli_seconds)
|
|||
}
|
||||
|
||||
/* Detach - we don't want to have to thrd_join() it */
|
||||
//thrd_detach(tid);
|
||||
// thrd_detach(tid);
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
|
@ -605,10 +649,8 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
const struct yml_node *port = yml_get_value(node, "port");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return mpd_new(
|
||||
yml_value_as_string(host),
|
||||
port != NULL ? yml_value_as_int(port) : 0,
|
||||
conf_to_particle(c, inherited));
|
||||
return mpd_new(yml_value_as_string(host), port != NULL ? yml_value_as_int(port) : 0,
|
||||
conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -616,7 +658,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"host", true, &conf_verify_string},
|
||||
{"port", false, &conf_verify_int},
|
||||
{"port", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
1100
modules/mpris.c
Normal file
1100
modules/mpris.c
Normal file
File diff suppressed because it is too large
Load diff
1484
modules/network.c
1484
modules/network.c
File diff suppressed because it is too large
Load diff
377
modules/niri-common.c
Normal file
377
modules/niri-common.c
Normal file
|
@ -0,0 +1,377 @@
|
|||
#include <errno.h>
|
||||
#include <json-c/json.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../log.h"
|
||||
#include "niri-common.h"
|
||||
|
||||
#define LOG_MODULE "niri:common"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
|
||||
static struct niri_socket instance = {
|
||||
.fd = -1,
|
||||
.abort_fd = -1,
|
||||
};
|
||||
|
||||
static void
|
||||
workspace_free(struct niri_workspace *workspace)
|
||||
{
|
||||
free(workspace->name);
|
||||
free(workspace);
|
||||
}
|
||||
|
||||
static void
|
||||
parser(char *response)
|
||||
{
|
||||
enum json_tokener_error error = json_tokener_success;
|
||||
struct json_object *json = json_tokener_parse_verbose(response, &error);
|
||||
if (error != json_tokener_success) {
|
||||
LOG_WARN("failed to parse niri socket's response");
|
||||
return;
|
||||
}
|
||||
|
||||
enum niri_event events = 0;
|
||||
struct json_object_iterator it = json_object_iter_begin(json);
|
||||
struct json_object_iterator end = json_object_iter_end(json);
|
||||
while (!json_object_iter_equal(&it, &end)) {
|
||||
char const *key = json_object_iter_peek_name(&it);
|
||||
|
||||
// "WorkspacesChanged": {
|
||||
// "workspaces": [
|
||||
// {
|
||||
// "id": 3,
|
||||
// "idx": 1,
|
||||
// "name": null,
|
||||
// "output": "DP-4",
|
||||
// "is_active": true,
|
||||
// "is_focused": true,
|
||||
// "active_window_id": 24
|
||||
// },
|
||||
// ...
|
||||
// ]
|
||||
// }
|
||||
if (strcmp(key, "WorkspacesChanged") == 0) {
|
||||
mtx_lock(&instance.mtx);
|
||||
tll_foreach(instance.workspaces, it) { tll_remove_and_free(instance.workspaces, it, workspace_free); }
|
||||
mtx_unlock(&instance.mtx);
|
||||
|
||||
json_object *obj = json_object_iter_peek_value(&it);
|
||||
json_object *workspaces = json_object_object_get(obj, "workspaces");
|
||||
|
||||
size_t length = json_object_array_length(workspaces);
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
json_object *ws_obj = json_object_array_get_idx(workspaces, i);
|
||||
|
||||
// only add workspaces on the current yambar's monitor
|
||||
struct json_object *output = json_object_object_get(ws_obj, "output");
|
||||
if (strcmp(instance.monitor, json_object_get_string(output)) != 0)
|
||||
continue;
|
||||
|
||||
struct niri_workspace *ws = calloc(1, sizeof(*ws));
|
||||
ws->idx = json_object_get_int(json_object_object_get(ws_obj, "idx"));
|
||||
ws->id = json_object_get_int(json_object_object_get(ws_obj, "id"));
|
||||
ws->active = json_object_get_boolean(json_object_object_get(ws_obj, "is_active"));
|
||||
ws->focused = json_object_get_boolean(json_object_object_get(ws_obj, "is_focused"));
|
||||
ws->empty = json_object_get_int(json_object_object_get(ws_obj, "active_window_id")) == 0;
|
||||
|
||||
char const *name = json_object_get_string(json_object_object_get(ws_obj, "name"));
|
||||
if (name)
|
||||
ws->name = strdup(name);
|
||||
|
||||
mtx_lock(&instance.mtx);
|
||||
bool inserted = false;
|
||||
tll_foreach(instance.workspaces, it)
|
||||
{
|
||||
if (it->item->idx > ws->idx) {
|
||||
tll_insert_before(instance.workspaces, it, ws);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted)
|
||||
tll_push_back(instance.workspaces, ws);
|
||||
mtx_unlock(&instance.mtx);
|
||||
|
||||
events |= workspaces_changed;
|
||||
}
|
||||
}
|
||||
|
||||
// "WorkspaceActivated": {
|
||||
// "id": 7,
|
||||
// "focused":true
|
||||
// }
|
||||
else if (strcmp(key, "WorkspaceActivated") == 0) {
|
||||
json_object *obj = json_object_iter_peek_value(&it);
|
||||
int id = json_object_get_int(json_object_object_get(obj, "id"));
|
||||
|
||||
mtx_lock(&instance.mtx);
|
||||
tll_foreach(instance.workspaces, it)
|
||||
{
|
||||
bool b = it->item->id == id;
|
||||
it->item->focused = b;
|
||||
it->item->active = b;
|
||||
}
|
||||
mtx_unlock(&instance.mtx);
|
||||
|
||||
events |= workspace_activated;
|
||||
}
|
||||
|
||||
// "WorkspaceActiveWindowChanged": {
|
||||
// "workspace_id": 3,
|
||||
// "active_window_id": 8
|
||||
// }
|
||||
else if (strcmp(key, "WorkspaceActiveWindowChanged") == 0) {
|
||||
json_object *obj = json_object_iter_peek_value(&it);
|
||||
int id = json_object_get_int(json_object_object_get(obj, "id"));
|
||||
bool empty = json_object_get_int(json_object_object_get(obj, "active_window_id")) == 0;
|
||||
|
||||
mtx_lock(&instance.mtx);
|
||||
tll_foreach(instance.workspaces, it)
|
||||
{
|
||||
if (it->item->id == id) {
|
||||
it->item->empty = empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mtx_unlock(&instance.mtx);
|
||||
|
||||
events |= workspace_active_window_changed;
|
||||
}
|
||||
|
||||
//
|
||||
// "KeyboardLayoutsChanged": {
|
||||
// "keyboard_layouts": {
|
||||
// "names": [
|
||||
// "English (US)",
|
||||
// "Russian"
|
||||
// ],
|
||||
// "current_idx": 0
|
||||
// }
|
||||
// }
|
||||
else if (strcmp(key, "KeyboardLayoutsChanged") == 0) {
|
||||
tll_foreach(instance.keyboard_layouts, it) { tll_remove_and_free(instance.keyboard_layouts, it, free); }
|
||||
|
||||
json_object *obj = json_object_iter_peek_value(&it);
|
||||
json_object *kb_layouts = json_object_object_get(obj, "keyboard_layouts");
|
||||
|
||||
instance.keyboard_layout_index = json_object_get_int(json_object_object_get(kb_layouts, "current_idx"));
|
||||
|
||||
json_object *names = json_object_object_get(kb_layouts, "names");
|
||||
size_t names_length = json_object_array_length(names);
|
||||
for (size_t i = 0; i < names_length; ++i) {
|
||||
char const *name = json_object_get_string(json_object_array_get_idx(names, i));
|
||||
tll_push_back(instance.keyboard_layouts, strdup(name));
|
||||
}
|
||||
|
||||
events |= keyboard_layouts_changed;
|
||||
}
|
||||
|
||||
// "KeyboardLayoutSwitched": {
|
||||
// "idx": 1
|
||||
// }
|
||||
else if (strcmp(key, "KeyboardLayoutSwitched") == 0) {
|
||||
json_object *obj = json_object_iter_peek_value(&it);
|
||||
instance.keyboard_layout_index = json_object_get_int(json_object_object_get(obj, "idx"));
|
||||
|
||||
events |= keyboard_layouts_switched;
|
||||
}
|
||||
|
||||
json_object_iter_next(&it);
|
||||
}
|
||||
|
||||
json_object_put(json);
|
||||
|
||||
mtx_lock(&instance.mtx);
|
||||
tll_foreach(instance.subscribers, it)
|
||||
{
|
||||
if (it->item->events & events)
|
||||
if (write(it->item->fd, &(uint64_t){1}, sizeof(uint64_t)) == -1)
|
||||
LOG_ERRNO("failed to write");
|
||||
}
|
||||
mtx_unlock(&instance.mtx);
|
||||
}
|
||||
|
||||
static int
|
||||
run(void *userdata)
|
||||
{
|
||||
static char msg[] = "\"EventStream\"\n";
|
||||
static char expected[] = "{\"Ok\":\"Handled\"}";
|
||||
|
||||
if (write(instance.fd, msg, sizeof(msg) / sizeof(msg[0])) == -1) {
|
||||
LOG_ERRNO("failed to sent message to niri socket");
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
static char buffer[8192];
|
||||
if (read(instance.fd, buffer, sizeof(buffer) / sizeof(buffer[0]) - 1) == -1) {
|
||||
LOG_ERRNO("failed to read response of niri socket");
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
char *saveptr;
|
||||
char *response = strtok_r(buffer, "\n", &saveptr);
|
||||
if (response == NULL || strcmp(expected, response) != 0) {
|
||||
// unexpected first response, something went wrong
|
||||
LOG_ERR("unexpected response of niri socket");
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
while ((response = strtok_r(NULL, "\n", &saveptr)) != NULL)
|
||||
parser(response);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
(struct pollfd){.fd = instance.abort_fd, .events = POLLIN},
|
||||
(struct pollfd){.fd = instance.fd, .events = POLLIN},
|
||||
};
|
||||
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
static char buffer[8192];
|
||||
ssize_t length = read(fds[1].fd, buffer, sizeof(buffer) / sizeof(buffer[0]));
|
||||
|
||||
if (length == 0)
|
||||
break;
|
||||
|
||||
if (length == -1) {
|
||||
if (errno == EAGAIN || errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("unable to read niri socket");
|
||||
break;
|
||||
}
|
||||
|
||||
buffer[length] = '\0';
|
||||
saveptr = NULL;
|
||||
response = strtok_r(buffer, "\n", &saveptr);
|
||||
do {
|
||||
parser(response);
|
||||
} while ((response = strtok_r(NULL, "\n", &saveptr)) != NULL);
|
||||
}
|
||||
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
struct niri_socket *
|
||||
niri_socket_open(char const *monitor)
|
||||
{
|
||||
if (instance.fd >= 0)
|
||||
return &instance;
|
||||
|
||||
char const *path = getenv("NIRI_SOCKET");
|
||||
if (path == NULL) {
|
||||
LOG_ERR("NIRI_SOCKET is empty. Is niri running?");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((instance.fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) {
|
||||
LOG_ERRNO("failed to create socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
|
||||
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(instance.fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
||||
LOG_ERRNO("failed to connect to niri socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((instance.abort_fd = eventfd(0, EFD_CLOEXEC)) == -1) {
|
||||
LOG_ERRNO("failed to create abort_fd");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (mtx_init(&instance.mtx, mtx_plain) != thrd_success) {
|
||||
LOG_ERR("failed to initialize mutex");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (thrd_create(&instance.thrd, run, NULL) != thrd_success) {
|
||||
LOG_ERR("failed to create thread");
|
||||
mtx_destroy(&instance.mtx);
|
||||
goto error;
|
||||
}
|
||||
|
||||
instance.monitor = monitor;
|
||||
|
||||
return &instance;
|
||||
|
||||
error:
|
||||
if (instance.fd >= 0)
|
||||
close(instance.fd);
|
||||
if (instance.abort_fd >= 0)
|
||||
close(instance.abort_fd);
|
||||
instance.fd = -1;
|
||||
instance.abort_fd = -1;
|
||||
instance.monitor = NULL;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
socket_close(void)
|
||||
{
|
||||
if (write(instance.abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t))
|
||||
LOG_ERRNO("failed to write to abort_fd");
|
||||
|
||||
thrd_join(instance.thrd, NULL);
|
||||
|
||||
close(instance.abort_fd);
|
||||
close(instance.fd);
|
||||
instance.abort_fd = -1;
|
||||
instance.fd = -1;
|
||||
|
||||
mtx_destroy(&instance.mtx);
|
||||
|
||||
tll_free_and_free(instance.subscribers, free);
|
||||
tll_free_and_free(instance.workspaces, workspace_free);
|
||||
tll_free_and_free(instance.keyboard_layouts, free);
|
||||
}
|
||||
|
||||
void
|
||||
niri_socket_close(void)
|
||||
{
|
||||
static once_flag flag = ONCE_FLAG_INIT;
|
||||
call_once(&flag, socket_close);
|
||||
}
|
||||
|
||||
int
|
||||
niri_socket_subscribe(enum niri_event events)
|
||||
{
|
||||
int fd = eventfd(0, EFD_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
LOG_ERRNO("failed to create eventfd");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct niri_subscriber *subscriber = calloc(1, sizeof(*subscriber));
|
||||
subscriber->events = events;
|
||||
subscriber->fd = fd;
|
||||
|
||||
mtx_lock(&instance.mtx);
|
||||
tll_push_back(instance.subscribers, subscriber);
|
||||
mtx_unlock(&instance.mtx);
|
||||
|
||||
return subscriber->fd;
|
||||
}
|
45
modules/niri-common.h
Normal file
45
modules/niri-common.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <threads.h>
|
||||
#include <tllist.h>
|
||||
|
||||
enum niri_event {
|
||||
workspaces_changed = (1 << 0),
|
||||
workspace_activated = (1 << 1),
|
||||
workspace_active_window_changed = (1 << 2),
|
||||
keyboard_layouts_changed = (1 << 3),
|
||||
keyboard_layouts_switched = (1 << 4),
|
||||
};
|
||||
|
||||
struct niri_subscriber {
|
||||
int events;
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct niri_workspace {
|
||||
int id;
|
||||
int idx;
|
||||
char *name;
|
||||
bool active;
|
||||
bool focused;
|
||||
bool empty;
|
||||
};
|
||||
|
||||
struct niri_socket {
|
||||
char const *monitor;
|
||||
int abort_fd;
|
||||
int fd;
|
||||
|
||||
tll(struct niri_subscriber *) subscribers;
|
||||
tll(struct niri_workspace *) workspaces;
|
||||
tll(char *) keyboard_layouts;
|
||||
size_t keyboard_layout_index;
|
||||
|
||||
thrd_t thrd;
|
||||
mtx_t mtx;
|
||||
};
|
||||
|
||||
struct niri_socket *niri_socket_open(char const *monitor);
|
||||
void niri_socket_close(void);
|
||||
int niri_socket_subscribe(enum niri_event events);
|
160
modules/niri-language.c
Normal file
160
modules/niri-language.c
Normal file
|
@ -0,0 +1,160 @@
|
|||
#include <errno.h>
|
||||
#include <json-c/json.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOG_MODULE "niri-language"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "niri-common.h"
|
||||
|
||||
#include "../log.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
struct niri_socket *niri;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
private->label->destroy(private->label);
|
||||
|
||||
free(private);
|
||||
|
||||
module_default_destroy(module);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *module)
|
||||
{
|
||||
return "niri-lang";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *module)
|
||||
{
|
||||
const struct private *private = module->private;
|
||||
|
||||
if (private->niri == NULL)
|
||||
return dynlist_exposable_new(&((struct exposable *){0}), 0, 0, 0);
|
||||
|
||||
mtx_lock(&module->lock);
|
||||
mtx_lock(&private->niri->mtx);
|
||||
|
||||
char *name = "???";
|
||||
size_t i = 0;
|
||||
tll_foreach(private->niri->keyboard_layouts, it)
|
||||
{
|
||||
if (i++ == private->niri->keyboard_layout_index)
|
||||
name = it->item;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){tag_new_string(module, "language", name)},
|
||||
.count = 1,
|
||||
};
|
||||
|
||||
struct exposable *exposable = private->label->instantiate(private->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
mtx_unlock(&private->niri->mtx);
|
||||
mtx_unlock(&module->lock);
|
||||
return exposable;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
|
||||
/* Ugly, but I didn't find better way for waiting
|
||||
* the monitor's name to be set */
|
||||
char const *monitor;
|
||||
do {
|
||||
monitor = module->bar->output_name(module->bar);
|
||||
usleep(50);
|
||||
} while (monitor == NULL);
|
||||
|
||||
private->niri = niri_socket_open(monitor);
|
||||
if (private->niri == NULL)
|
||||
return 1;
|
||||
|
||||
int fd = niri_socket_subscribe(keyboard_layouts_changed | keyboard_layouts_switched);
|
||||
if (fd == -1) {
|
||||
niri_socket_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
(struct pollfd){.fd = module->abort_fd, .events = POLLIN},
|
||||
(struct pollfd){.fd = fd, .events = POLLIN},
|
||||
};
|
||||
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
if (read(fds[1].fd, &(uint64_t){0}, sizeof(uint64_t)) == -1)
|
||||
LOG_ERRNO("failed to read from eventfd");
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
}
|
||||
|
||||
niri_socket_close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
niri_language_new(struct particle *label)
|
||||
{
|
||||
struct private *private = calloc(1, sizeof(struct private));
|
||||
private->label = label;
|
||||
|
||||
struct module *module = module_common_new();
|
||||
module->private = private;
|
||||
module->run = &run;
|
||||
module->destroy = &destroy;
|
||||
module->content = &content;
|
||||
module->description = &description;
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(struct yml_node const *node, struct conf_inherit inherited)
|
||||
{
|
||||
struct yml_node const *content = yml_get_value(node, "content");
|
||||
return niri_language_new(conf_to_particle(content, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static struct attr_info const attrs[] = {
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_niri_language_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_niri_language_iface")));
|
||||
#endif
|
163
modules/niri-workspaces.c
Normal file
163
modules/niri-workspaces.c
Normal file
|
@ -0,0 +1,163 @@
|
|||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LOG_MODULE "niri-workspaces"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "niri-common.h"
|
||||
|
||||
#include "../log.h"
|
||||
#include "../particles/dynlist.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
struct niri_socket *niri;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
private->label->destroy(private->label);
|
||||
|
||||
free(private);
|
||||
|
||||
module_default_destroy(module);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *module)
|
||||
{
|
||||
return "niri-ws";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *module)
|
||||
{
|
||||
struct private const *private = module->private;
|
||||
|
||||
if (private->niri == NULL)
|
||||
return dynlist_exposable_new(&((struct exposable *){0}), 0, 0, 0);
|
||||
|
||||
mtx_lock(&module->lock);
|
||||
mtx_lock(&private->niri->mtx);
|
||||
|
||||
size_t i = 0;
|
||||
struct exposable *exposable[tll_length(private->niri->workspaces)];
|
||||
tll_foreach(private->niri->workspaces, it)
|
||||
{
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag*[]){
|
||||
tag_new_int(module, "id", it->item->idx),
|
||||
tag_new_string(module, "name", it->item->name),
|
||||
tag_new_bool(module, "active", it->item->active),
|
||||
tag_new_bool(module, "focused", it->item->focused),
|
||||
tag_new_bool(module, "empty", it->item->empty),
|
||||
},
|
||||
.count = 5,
|
||||
};
|
||||
|
||||
exposable[i++] = private->label->instantiate(private->label, &tags);
|
||||
tag_set_destroy(&tags);
|
||||
}
|
||||
|
||||
mtx_unlock(&private->niri->mtx);
|
||||
mtx_unlock(&module->lock);
|
||||
return dynlist_exposable_new(exposable, i, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *module)
|
||||
{
|
||||
struct private *private = module->private;
|
||||
|
||||
/* Ugly, but I didn't find better way for waiting
|
||||
* the monitor's name to be set */
|
||||
char const *monitor;
|
||||
do {
|
||||
monitor = module->bar->output_name(module->bar);
|
||||
usleep(50);
|
||||
} while (monitor == NULL);
|
||||
|
||||
private->niri = niri_socket_open(monitor);
|
||||
if (private->niri == NULL)
|
||||
return 1;
|
||||
|
||||
int fd = niri_socket_subscribe(workspaces_changed | workspace_activated | workspace_active_window_changed);
|
||||
if (fd == -1) {
|
||||
niri_socket_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
(struct pollfd){.fd = module->abort_fd, .events = POLLIN},
|
||||
(struct pollfd){.fd = fd, .events = POLLIN},
|
||||
};
|
||||
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
break;
|
||||
|
||||
if (read(fds[1].fd, &(uint64_t){0}, sizeof(uint64_t)) == -1)
|
||||
LOG_ERRNO("failed to read from eventfd");
|
||||
|
||||
module->bar->refresh(module->bar);
|
||||
}
|
||||
|
||||
niri_socket_close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
niri_workspaces_new(struct particle *label)
|
||||
{
|
||||
struct private *private = calloc(1, sizeof(struct private));
|
||||
private->label = label;
|
||||
|
||||
struct module *module = module_common_new();
|
||||
module->private = private;
|
||||
module->run = &run;
|
||||
module->destroy = &destroy;
|
||||
module->content = &content;
|
||||
module->description = &description;
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(struct yml_node const *node, struct conf_inherit inherited)
|
||||
{
|
||||
struct yml_node const *content = yml_get_value(node, "content");
|
||||
return niri_workspaces_new(conf_to_particle(content, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static struct attr_info const attrs[] = {
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_niri_workspaces_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_niri_workspaces_iface")));
|
||||
#endif
|
1025
modules/pipewire.c
Normal file
1025
modules/pipewire.c
Normal file
File diff suppressed because it is too large
Load diff
523
modules/pulse.c
Normal file
523
modules/pulse.c
Normal file
|
@ -0,0 +1,523 @@
|
|||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#define LOG_MODULE "pulse"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private
|
||||
{
|
||||
char *sink_name;
|
||||
char *source_name;
|
||||
struct particle *label;
|
||||
|
||||
bool online;
|
||||
|
||||
bool sink_online;
|
||||
pa_cvolume sink_volume;
|
||||
bool sink_muted;
|
||||
char *sink_port;
|
||||
uint32_t sink_index;
|
||||
|
||||
bool source_online;
|
||||
pa_cvolume source_volume;
|
||||
bool source_muted;
|
||||
char *source_port;
|
||||
uint32_t source_index;
|
||||
|
||||
int refresh_timer_fd;
|
||||
bool refresh_scheduled;
|
||||
|
||||
pa_mainloop *mainloop;
|
||||
pa_context *context;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
priv->label->destroy(priv->label);
|
||||
free(priv->sink_name);
|
||||
free(priv->source_name);
|
||||
free(priv->sink_port);
|
||||
free(priv->source_port);
|
||||
free(priv);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "pulse";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
pa_volume_t sink_volume_max = pa_cvolume_max(&priv->sink_volume);
|
||||
pa_volume_t source_volume_max = pa_cvolume_max(&priv->source_volume);
|
||||
int sink_percent = round(100.0 * sink_volume_max / PA_VOLUME_NORM);
|
||||
int source_percent = round(100.0 * source_volume_max / PA_VOLUME_NORM);
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_bool(mod, "online", priv->online),
|
||||
|
||||
tag_new_bool(mod, "sink_online", priv->sink_online),
|
||||
tag_new_int_range(mod, "sink_percent", sink_percent, 0, 100),
|
||||
tag_new_bool(mod, "sink_muted", priv->sink_muted),
|
||||
tag_new_string(mod, "sink_port", priv->sink_port),
|
||||
|
||||
tag_new_bool(mod, "source_online", priv->source_online),
|
||||
tag_new_int_range(mod, "source_percent", source_percent, 0, 100),
|
||||
tag_new_bool(mod, "source_muted", priv->source_muted),
|
||||
tag_new_string(mod, "source_port", priv->source_port),
|
||||
},
|
||||
.count = 9,
|
||||
};
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
struct exposable *exposable = priv->label->instantiate(priv->label, &tags);
|
||||
|
||||
tag_set_destroy(&tags);
|
||||
return exposable;
|
||||
}
|
||||
|
||||
static const char *
|
||||
context_error(pa_context *c)
|
||||
{
|
||||
return pa_strerror(pa_context_errno(c));
|
||||
}
|
||||
|
||||
static void
|
||||
abort_event_cb(pa_mainloop_api *api, pa_io_event *event, int fd, pa_io_event_flags_t flags, void *userdata)
|
||||
{
|
||||
struct module *mod = userdata;
|
||||
struct private *priv = mod->private;
|
||||
|
||||
pa_context_disconnect(priv->context);
|
||||
}
|
||||
|
||||
static void
|
||||
refresh_timer_cb(pa_mainloop_api *api, pa_io_event *event, int fd, pa_io_event_flags_t flags, void *userdata)
|
||||
{
|
||||
struct module *mod = userdata;
|
||||
struct private *priv = mod->private;
|
||||
|
||||
// Drain the refresh timer.
|
||||
uint64_t n;
|
||||
if (read(priv->refresh_timer_fd, &n, sizeof n) < 0)
|
||||
LOG_ERRNO("failed to read from timerfd");
|
||||
|
||||
// Clear the refresh flag.
|
||||
priv->refresh_scheduled = false;
|
||||
|
||||
// Refresh the bar.
|
||||
mod->bar->refresh(mod->bar);
|
||||
}
|
||||
|
||||
// Refresh the bar after a small delay. Without the delay, the bar
|
||||
// would be refreshed multiple times per event (e.g., a volume change),
|
||||
// and sometimes the active port would be reported incorrectly for a
|
||||
// brief moment. (This behavior was observed with PipeWire 0.3.61.)
|
||||
static void
|
||||
schedule_refresh(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
// Do nothing if a refresh has already been scheduled.
|
||||
if (priv->refresh_scheduled)
|
||||
return;
|
||||
|
||||
// Start the refresh timer.
|
||||
struct itimerspec t = {
|
||||
.it_interval = {.tv_sec = 0, .tv_nsec = 0},
|
||||
.it_value = {.tv_sec = 0, .tv_nsec = 50000000},
|
||||
};
|
||||
timerfd_settime(priv->refresh_timer_fd, 0, &t, NULL);
|
||||
|
||||
// Set the refresh flag.
|
||||
priv->refresh_scheduled = true;
|
||||
}
|
||||
|
||||
static void
|
||||
set_server_online(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
priv->online = true;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
schedule_refresh(mod);
|
||||
}
|
||||
|
||||
static void
|
||||
set_server_offline(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
priv->online = false;
|
||||
priv->sink_online = false;
|
||||
priv->source_online = false;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
schedule_refresh(mod);
|
||||
}
|
||||
|
||||
static void
|
||||
set_sink_info(struct module *mod, const pa_sink_info *sink_info)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
free(priv->sink_port);
|
||||
|
||||
priv->sink_online = true;
|
||||
priv->sink_index = sink_info->index;
|
||||
priv->sink_volume = sink_info->volume;
|
||||
priv->sink_muted = sink_info->mute;
|
||||
priv->sink_port = sink_info->active_port != NULL ? strdup(sink_info->active_port->description) : NULL;
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
schedule_refresh(mod);
|
||||
}
|
||||
|
||||
static void
|
||||
set_sink_offline(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
priv->sink_online = false;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
schedule_refresh(mod);
|
||||
}
|
||||
|
||||
static void
|
||||
set_source_info(struct module *mod, const pa_source_info *source_info)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
free(priv->source_port);
|
||||
|
||||
priv->source_online = true;
|
||||
priv->source_index = source_info->index;
|
||||
priv->source_volume = source_info->volume;
|
||||
priv->source_muted = source_info->mute;
|
||||
priv->source_port = source_info->active_port != NULL ? strdup(source_info->active_port->description) : NULL;
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
schedule_refresh(mod);
|
||||
}
|
||||
|
||||
static void
|
||||
set_source_offline(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
priv->source_online = false;
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
schedule_refresh(mod);
|
||||
}
|
||||
|
||||
static void
|
||||
sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
|
||||
{
|
||||
struct module *mod = userdata;
|
||||
|
||||
if (eol < 0) {
|
||||
LOG_ERR("failed to get sink info: %s", context_error(c));
|
||||
set_sink_offline(mod);
|
||||
} else if (eol == 0) {
|
||||
set_sink_info(mod, i);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
source_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata)
|
||||
{
|
||||
struct module *mod = userdata;
|
||||
|
||||
if (eol < 0) {
|
||||
LOG_ERR("failed to get source info: %s", context_error(c));
|
||||
set_source_offline(mod);
|
||||
} else if (eol == 0) {
|
||||
set_source_info(mod, i);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
server_info_cb(pa_context *c, const pa_server_info *i, void *userdata)
|
||||
{
|
||||
LOG_INFO("%s, version %s", i->server_name, i->server_version);
|
||||
}
|
||||
|
||||
static void
|
||||
get_sink_info_by_name(pa_context *c, const char *name, void *userdata)
|
||||
{
|
||||
pa_operation *o = pa_context_get_sink_info_by_name(c, name, sink_info_cb, userdata);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
static void
|
||||
get_source_info_by_name(pa_context *c, const char *name, void *userdata)
|
||||
{
|
||||
pa_operation *o = pa_context_get_source_info_by_name(c, name, source_info_cb, userdata);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
static void
|
||||
get_sink_info_by_index(pa_context *c, uint32_t index, void *userdata)
|
||||
{
|
||||
pa_operation *o = pa_context_get_sink_info_by_index(c, index, sink_info_cb, userdata);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
static void
|
||||
get_source_info_by_index(pa_context *c, uint32_t index, void *userdata)
|
||||
{
|
||||
pa_operation *o = pa_context_get_source_info_by_index(c, index, source_info_cb, userdata);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
static void
|
||||
get_server_info(pa_context *c, void *userdata)
|
||||
{
|
||||
pa_operation *o = pa_context_get_server_info(c, server_info_cb, userdata);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
static void
|
||||
subscribe(pa_context *c, void *userdata)
|
||||
{
|
||||
pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_SERVER | PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
|
||||
pa_operation *o = pa_context_subscribe(c, mask, NULL, userdata);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
|
||||
static pa_context *connect_to_server(struct module *mod);
|
||||
|
||||
static void
|
||||
context_state_change_cb(pa_context *c, void *userdata)
|
||||
{
|
||||
struct module *mod = userdata;
|
||||
struct private *priv = mod->private;
|
||||
|
||||
pa_context_state_t state = pa_context_get_state(c);
|
||||
switch (state) {
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_READY:
|
||||
set_server_online(mod);
|
||||
subscribe(c, mod);
|
||||
get_server_info(c, mod);
|
||||
get_sink_info_by_name(c, priv->sink_name, mod);
|
||||
get_source_info_by_name(c, priv->source_name, mod);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
LOG_WARN("connection lost");
|
||||
set_server_offline(mod);
|
||||
pa_context_unref(priv->context);
|
||||
priv->context = connect_to_server(mod);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
LOG_DBG("connection terminated");
|
||||
set_server_offline(mod);
|
||||
pa_mainloop_quit(priv->mainloop, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
subscription_event_cb(pa_context *c, pa_subscription_event_type_t event_type, uint32_t index, void *userdata)
|
||||
{
|
||||
struct module *mod = userdata;
|
||||
struct private *priv = mod->private;
|
||||
|
||||
int facility = event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
||||
int type = event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
|
||||
|
||||
switch (facility) {
|
||||
case PA_SUBSCRIPTION_EVENT_SERVER:
|
||||
get_sink_info_by_name(c, priv->sink_name, mod);
|
||||
get_source_info_by_name(c, priv->source_name, mod);
|
||||
break;
|
||||
|
||||
case PA_SUBSCRIPTION_EVENT_SINK:
|
||||
if (index == priv->sink_index) {
|
||||
if (type == PA_SUBSCRIPTION_EVENT_CHANGE)
|
||||
get_sink_info_by_index(c, index, mod);
|
||||
else if (type == PA_SUBSCRIPTION_EVENT_REMOVE)
|
||||
set_sink_offline(mod);
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_SUBSCRIPTION_EVENT_SOURCE:
|
||||
if (index == priv->source_index) {
|
||||
if (type == PA_SUBSCRIPTION_EVENT_CHANGE)
|
||||
get_source_info_by_index(c, index, mod);
|
||||
else if (type == PA_SUBSCRIPTION_EVENT_REMOVE)
|
||||
set_source_offline(mod);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static pa_context *
|
||||
connect_to_server(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
|
||||
// Create connection context.
|
||||
pa_mainloop_api *api = pa_mainloop_get_api(priv->mainloop);
|
||||
pa_context *c = pa_context_new(api, "yambar");
|
||||
if (c == NULL) {
|
||||
LOG_ERR("failed to create PulseAudio connection context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Register callback functions.
|
||||
pa_context_set_state_callback(c, context_state_change_cb, mod);
|
||||
pa_context_set_subscribe_callback(c, subscription_event_cb, mod);
|
||||
|
||||
// Connect to server.
|
||||
pa_context_flags_t flags = PA_CONTEXT_NOFAIL | PA_CONTEXT_NOAUTOSPAWN;
|
||||
if (pa_context_connect(c, NULL, flags, NULL) < 0) {
|
||||
LOG_ERR("failed to connect to PulseAudio server: %s", context_error(c));
|
||||
pa_context_unref(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
struct private *priv = mod->private;
|
||||
int ret = -1;
|
||||
|
||||
// Create main loop.
|
||||
priv->mainloop = pa_mainloop_new();
|
||||
if (priv->mainloop == NULL) {
|
||||
LOG_ERR("failed to create PulseAudio main loop");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create refresh timer.
|
||||
priv->refresh_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
if (priv->refresh_timer_fd < 0) {
|
||||
LOG_ERRNO("failed to create timerfd");
|
||||
pa_mainloop_free(priv->mainloop);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Connect to server.
|
||||
priv->context = connect_to_server(mod);
|
||||
if (priv->context == NULL) {
|
||||
pa_mainloop_free(priv->mainloop);
|
||||
close(priv->refresh_timer_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Poll refresh timer and abort event.
|
||||
pa_mainloop_api *api = pa_mainloop_get_api(priv->mainloop);
|
||||
api->io_new(api, priv->refresh_timer_fd, PA_IO_EVENT_INPUT, refresh_timer_cb, mod);
|
||||
api->io_new(api, mod->abort_fd, PA_IO_EVENT_INPUT | PA_IO_EVENT_HANGUP, abort_event_cb, mod);
|
||||
|
||||
// Run main loop.
|
||||
if (pa_mainloop_run(priv->mainloop, &ret) < 0) {
|
||||
LOG_ERR("PulseAudio main loop error");
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
pa_context_unref(priv->context);
|
||||
pa_mainloop_free(priv->mainloop);
|
||||
close(priv->refresh_timer_fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
pulse_new(const char *sink_name, const char *source_name, struct particle *label)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof *priv);
|
||||
priv->label = label;
|
||||
priv->sink_name = strdup(sink_name);
|
||||
priv->source_name = strdup(source_name);
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = priv;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||
{
|
||||
const struct yml_node *sink = yml_get_value(node, "sink");
|
||||
const struct yml_node *source = yml_get_value(node, "source");
|
||||
const struct yml_node *content = yml_get_value(node, "content");
|
||||
|
||||
return pulse_new(sink != NULL ? yml_value_as_string(sink) : "@DEFAULT_SINK@",
|
||||
source != NULL ? yml_value_as_string(source) : "@DEFAULT_SOURCE@",
|
||||
conf_to_particle(content, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"sink", false, &conf_verify_string},
|
||||
{"source", false, &conf_verify_string},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_pulse_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_pulse_iface")));
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue