Compare commits

..

No commits in common. "master" and "1.6.2" have entirely different histories.

129 changed files with 3282 additions and 14821 deletions

View file

@ -19,17 +19,13 @@ packages:
- json-c-dev
- libmpdclient-dev
- alsa-lib-dev
- pulseaudio-dev
- pipewire-dev
- ttf-dejavu
- gcovr
- python3
- py3-pip
- flex
- bison
sources:
- https://git.sr.ht/~dnkl/yambar
- https://codeberg.org/dnkl/yambar
# triggers:
# - action: email
@ -37,10 +33,10 @@ sources:
# to: <comitter>
tasks:
- fcft: |
cd yambar/subprojects
git clone https://codeberg.org/dnkl/fcft.git
cd ../..
- codespell: |
pip install codespell
cd yambar
~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd
- 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

View file

@ -1,24 +0,0 @@
---
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

View file

@ -1,17 +0,0 @@
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

98
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,98 @@
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

View file

@ -1,132 +0,0 @@
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 ../..

View file

@ -1,535 +1,50 @@
# 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: dont error out on systems where SMT has been disabled
([#172][172]).
* examples/dwl-tags: updated parsing of `output` name ([#178][178]).
* sway-xkb: dont crash when Sway sends an _”added”_ event for a
device yambar is already tracking ([#177][177]).
* Crash when a particle is “too wide”, and tries to render outside the
bar ([#198][198]).
* string: crash when failing to convert string to UTF-32.
* script: only first transaction processed when receiving multiple
transactions in a single batch ([#221][221]).
* network: missing SSID (recent kernels, or possibly wireless drivers,
no longer provide the SSID in the `NL80211_CMD_NEW_STATION`
response) ([#226][226]).
* sway-xkb: crash when compositor presents multiple inputs with
identical IDs ([#229][229]).
[169]: https://codeberg.org/dnkl/yambar/issues/169
[172]: https://codeberg.org/dnkl/yambar/issues/172
[178]: https://codeberg.org/dnkl/yambar/issues/178
[177]: https://codeberg.org/dnkl/yambar/issues/177
[198]: https://codeberg.org/dnkl/yambar/issues/198
[221]: https://codeberg.org/dnkl/yambar/issues/221
[226]: https://codeberg.org/dnkl/yambar/issues/226
[229]: https://codeberg.org/dnkl/yambar/issues/229
### Contributors
* Baptiste Daroussin
* Horus
* Johannes
* Leonardo Gibrowski Faé
* Leonardo Neumann
* Midgard
* Ogromny
* Peter Rice
* Timur Celik
* Willem van de Krol
* hiog
## 1.8.0
### Added
* ramp: can now have custom min and max values
([#103](https://codeberg.org/dnkl/yambar/issues/103)).
* border: new decoration.
* i3/sway: new boolean tag: `empty`
([#139](https://codeberg.org/dnkl/yambar/issues/139)).
* mem: a module handling system memory monitoring
* cpu: a module offering cpu usage monitoring
* removables: support for audio CDs
([#146](https://codeberg.org/dnkl/yambar/issues/146)).
* removables: new boolean tag: `audio`.
### Changed
* fcft >= 3.0 is now required.
* Made `libmpdclient` an optional dependency
* battery: unknown battery states are now mapped to unknown, instead
of discharging.
* Wayland: the bar no longer exits when the monitor is
disabled/unplugged ([#106](https://codeberg.org/dnkl/yambar/issues/106)).
### Fixed
* `left-margin` and `right-margin` from being rejected as invalid
options.
* Crash when `udev_monitor_receive_device()` returned `NULL`. This
affected the “backlight”, “battery” and “removables” modules
([#109](https://codeberg.org/dnkl/yambar/issues/109)).
* foreign-toplevel: update bar when a top-level is closed.
* Bar not being mapped on an output before at least one module has
“refreshed” it ([#116](https://codeberg.org/dnkl/yambar/issues/116)).
* network: failure to retrieve wireless attributes (SSID, RX/TX
bitrate, signal strength etc).
* Integer options that were supposed to be >= 0 were incorrectly
allowed, leading to various bad things; including yambar crashing,
or worse, the compositor crashing
([#129](https://codeberg.org/dnkl/yambar/issues/129)).
* kib/kb, mib/mb and gib/gb formatters were inverted.
### Contributors
* [sochotnicky](https://codeberg.org/sochotnicky)
* Alexandre Acebedo
* anb
* Baptiste Daroussin
* Catterwocky
* horus645
* Jan Beich
* mz
* natemaia
* nogerine
* Soc Virnyl S. Estela
* Vincent Fischer
## 1.7.0
### Added
* i3: `persistent` attribute, allowing persistent workspaces
([#72](https://codeberg.org/dnkl/yambar/issues/72)).
* bar: `border.{left,right,top,bottom}-width`, allowing the width of
each side of the border to be configured
individually. `border.width` is now a short-hand for setting all
four borders to the same value
([#77](https://codeberg.org/dnkl/yambar/issues/77)).
* bar: `layer: top|bottom`, allowing the layer which the bar is
rendered on to be changed. Wayland only - ignored on X11.
* river: `all-monitors: false|true`.
* `-d,--log-level=info|warning|error|none` command line option
([#84](https://codeberg.org/dnkl/yambar/issues/84)).
* river: support for the river-status protocol, version 2 (urgent
views).
* `online` tag to the `alsa` module.
* alsa: `volume` and `muted` options, allowing you to configure which
channels to use as source for the volume level and muted state.
* foreign-toplevel: Wayland module that provides information about
currently opened windows.
* alsa: support for capture devices.
* network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags.
* network: `poll-interval` option (for the new `signal` and
`*-bitrate` tags).
* tags: percentage tag formatter, for range tags: `{tag_name:%}`.
* tags: kb/mb/gb, and kib/mib/gib tag formatters.
* clock: add a config option to show UTC time.
### Changed
* bar: do not add `spacing` around empty (zero-width) modules.
* alsa: do not error out if we fail to connect to the ALSA device, or
if we get disconnected. Instead, keep retrying until we succeed
([#86](https://codeberg.org/dnkl/yambar/issues/86)).
### Fixed
* `yambar --backend=wayland` always erroring out with _”yambar was
compiled without the Wayland backend”_.
* Regression: `{where}` tag not being expanded in progress-bar
`on-click` handlers.
* `alsa` module causing yambar to use 100% CPU if the ALSA device is
disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)).
### Contributors
* [paemuri](https://codeberg.org/paemuri)
* [ericonr](https://codeberg.org/ericonr)
* [Nulo](https://nulo.in)
## 1.6.2
### Added
* Text shaping support.
* Support for middle and right mouse buttons, mouse wheel and trackpad
scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)).
scrolling (https://codeberg.org/dnkl/yambar/issues/39).
* script: polling mode. See the new `poll-interval` option
([#67](https://codeberg.org/dnkl/yambar/issues/67)).
(https://codeberg.org/dnkl/yambar/issues/67).
### Changed
* doc: split up **yambar-modules**(5) into multiple man pages, one for
each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)).
each module (https://codeberg.org/dnkl/yambar/issues/15).
* fcft >= 2.4.0 is now required.
* sway-xkb: non-keyboard inputs are now ignored
([#51](https://codeberg.org/dnkl/yambar/issues/51)).
(https://codeberg.org/dnkl/yambar/issues/51).
* battery: dont terminate (causing last status to “freeze”) when
failing to update; retry again later
([#44](https://codeberg.org/dnkl/yambar/issues/44)).
(https://codeberg.org/dnkl/yambar/issues/44).
* battery: differentiate "Not Charging" and "Discharging" in state
tag of battery module.
([#57](https://codeberg.org/dnkl/yambar/issues/57)).
(https://codeberg.org/dnkl/yambar/issues/57).
* string: use HORIZONTAL ELLIPSIS instead of three regular periods
when truncating a string
([#73](https://codeberg.org/dnkl/yambar/issues/73)).
(https://codeberg.org/dnkl/yambar/issues/73).
### Fixed
* Crash when merging non-dictionary anchors in the YAML configuration
([#32](https://codeberg.org/dnkl/yambar/issues/32)).
(https://codeberg.org/dnkl/yambar/issues/32).
* Crash in the `ramp` particle when the tags value was out-of-bounds
([#45](https://codeberg.org/dnkl/yambar/issues/45)).
(https://codeberg.org/dnkl/yambar/issues/45).
* Crash when a string particle contained `{}`
([#48](https://codeberg.org/dnkl/yambar/issues/48)).
(https://codeberg.org/dnkl/yambar/issues/48).
* `script` module rejecting range tag end values containing the digit
`9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)).
`9` (https://codeberg.org/dnkl/yambar/issues/60).
### Contributors
@ -544,7 +59,7 @@
* i3: workspaces with numerical names are sorted separately from
non-numerically named workspaces
([#30](https://codeberg.org/dnkl/yambar/issues/30)).
(https://codeberg.org/dnkl/yambar/issues/30).
### Fixed
@ -552,7 +67,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 ([#25](https://codeberg.org/dnkl/yambar/issues/25)).
values (https://codeberg.org/dnkl/yambar/issues/25).
## 1.6.0
@ -561,17 +76,17 @@
* alsa: `percent` tag. This is an integer tag that represents the
current volume as a percentage value
([#10](https://codeberg.org/dnkl/yambar/issues/10)).
(https://codeberg.org/dnkl/yambar/issues/10).
* river: added documentation
([#9](https://codeberg.org/dnkl/yambar/issues/9)).
(https://codeberg.org/dnkl/yambar/issues/9).
* script: new module, adds support for custom user scripts
([#11](https://codeberg.org/dnkl/yambar/issues/11)).
(https://codeberg.org/dnkl/yambar/issues/11).
* mpd: `volume` tag. This is a range tag that represents MPD's current
volume in percentage (0-100)
* i3: `sort` configuration option, that controls how the workspace
list is sorted. Can be set to one of `none`, `ascending` or
`descending`. Default is `none`
([#17](https://codeberg.org/dnkl/yambar/issues/17)).
(https://codeberg.org/dnkl/yambar/issues/17).
* i3: `mode` tag: the name of the currently active mode
@ -581,12 +96,12 @@
error”_.
* Memory leak when a YAML parsing error was encountered.
* clock: update every second when necessary
([#12](https://codeberg.org/dnkl/yambar/issues/12)).
(https://codeberg.org/dnkl/yambar/issues/12).
* mpd: fix compilation with clang
([#16](https://codeberg.org/dnkl/yambar/issues/16)).
(https://codeberg.org/dnkl/yambar/issues/16).
* Crash when the alpha component in a color value was 0.
* XCB: Fallback to non-primary monitor when the primary monitor is
disconnected ([#20](https://codeberg.org/dnkl/yambar/issues/20))
disconnected (https://codeberg.org/dnkl/yambar/issues/20)
### Contributors

View file

@ -1,8 +1,7 @@
pkgname=yambar
pkgver=1.11.0
pkgver=1.6.2
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)
@ -16,9 +15,7 @@ depends=(
'libudev.so'
'json-c'
'libmpdclient'
'libpulse'
'pipewire'
'fcft>=3.0.0' 'fcft<4.0.0')
'fcft>=2.4.0')
optdepends=('xcb-util-errors: better X error messages')
source=()

View file

@ -1,5 +1,5 @@
pkgname=yambar-wayland
pkgver=1.11.0
pkgver=1.6.2
pkgrel=1
pkgdesc="Simplistic and highly configurable status panel for Wayland"
arch=('x86_64' 'aarch64')
@ -16,11 +16,8 @@ depends=(
'libudev.so'
'json-c'
'libmpdclient'
'libpulse'
'pipewire'
'fcft>=3.0.0' 'fcft<4.0.0')
'fcft>=2.4.0')
source=()
changelog=CHANGELOG.md
pkgver() {
cd ../.git &> /dev/null && git describe --tags --long | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' ||

View file

@ -1,8 +1,6 @@
[![CI status](https://ci.codeberg.org/api/badges/dnkl/yambar/status.svg)](https://ci.codeberg.org/dnkl/yambar)
# Yambar
[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg?columns=4)](https://repology.org/project/yambar/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg)](https://repology.org/project/yambar/versions)
## Index
@ -59,9 +57,9 @@ bar:
right:
- clock:
content:
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
- string: {text: "{date}", right-margin: 5}
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
- string: {text: "{time}"}
```
@ -78,17 +76,10 @@ 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))
@ -107,7 +98,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 setup --buildtype=release ../..
meson --buildtype=release ../..
```
Optionally, explicitly disable a backend (or enable, if you want a

View file

@ -7,10 +7,11 @@
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);
};

169
bar/bar.c
View file

@ -1,15 +1,15 @@
#include "bar.h"
#include "private.h"
#include <assert.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <threads.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/eventfd.h>
@ -18,17 +18,15 @@
#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/right groups.
* Calculate total width of left/center/rigth groups.
* Note: begin_expose() must have been called
*/
static void
@ -40,33 +38,23 @@ 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];
if (e->width > 0)
*left += b->left_spacing + e->width + b->right_spacing;
*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];
if (e->width > 0)
*center += b->left_spacing + e->width + b->right_spacing;
*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];
if (e->width > 0)
*right += b->left_spacing + e->width + b->right_spacing;
*right += b->left_spacing + e->width + b->right_spacing;
}
/* No spacing on the edges (that's what the margins are for) */
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);
*left -= b->left_spacing + b->right_spacing;
*center -= b->left_spacing + b->right_spacing;
*right -= b->left_spacing + b->right_spacing;
}
static void
@ -75,26 +63,20 @@ 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_OVER, pix, &bar->border.color, 4,
(pixman_rectangle16_t[]){
/* Left */
{0, 0, bar->border.left_width, bar->height_with_border},
PIXMAN_OP_SRC, pix, &bar->background, 1,
&(pixman_rectangle16_t){0, 0, bar->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},
});
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},
});
}
for (size_t i = 0; i < bar->left.count; i++) {
struct module *m = bar->left.mods[i];
@ -102,7 +84,6 @@ 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++) {
@ -111,7 +92,6 @@ 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++) {
@ -120,49 +100,42 @@ 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, &center_width, &right_width);
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);
int y = bar->border.width;
int x = bar->border.width + bar->left_margin - bar->left_spacing;
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);
if (e->width > 0)
x += bar->left_spacing + e->width + bar->right_spacing;
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);
if (e->width > 0)
x += bar->left_spacing + e->width + bar->right_spacing;
x += bar->left_spacing + e->width + bar->right_spacing;
}
x = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width);
x = bar->width - (
right_width +
bar->left_spacing +
bar->right_margin +
bar->border.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);
if (e->width > 0)
x += bar->left_spacing + e->width + bar->right_spacing;
x += bar->left_spacing + e->width + bar->right_spacing;
}
bar->backend.iface->commit(_bar);
}
static void
refresh(const struct bar *bar)
{
@ -177,20 +150,16 @@ 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.top_width || y >= (bar->height_with_border - bar->border.bottom_width))
|| (x < bar->border.left_width || x >= (bar->width - bar->border.right_width))) {
if ((y < bar->border.width ||
y >= (bar->height_with_border - bar->border.width)) ||
(x < bar->border.width || x >= (bar->width - bar->border.width)))
{
set_cursor(_bar, "left_ptr");
return;
}
@ -198,13 +167,10 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x,
int left_width, center_width, right_width;
calculate_widths(bar, &left_width, &center_width, &right_width);
int mx = bar->border.left_width + bar->left_margin - bar->left_spacing;
int mx = bar->border.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)
@ -219,9 +185,6 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x,
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)
@ -232,14 +195,14 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x,
mx += e->width + bar->right_spacing;
}
mx = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width);
mx = bar->width - (right_width
+ bar->left_spacing +
bar->right_margin +
bar->border.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)
@ -273,7 +236,7 @@ run(struct bar *_bar)
{
struct private *bar = _bar->private;
bar->height_with_border = bar->height + bar->border.top_width + bar->border.bottom_width;
bar->height_with_border = bar->height + 2 * bar->border.width;
if (!bar->backend.iface->setup(_bar)) {
bar->backend.iface->cleanup(_bar);
@ -283,12 +246,11 @@ run(struct bar *_bar)
}
set_cursor(_bar, "left_ptr");
expose(_bar);
/* Start modules */
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)];
thrd_t thrd_left[bar->left.count];
thrd_t thrd_center[bar->center.count];
thrd_t thrd_right[bar->right.count];
for (size_t i = 0; i < bar->left.count; i++) {
struct module *mod = bar->left.mods[i];
@ -323,26 +285,20 @@ 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) {
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);
}
if (mod_ret != 0)
LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, 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) {
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);
}
if (mod_ret != 0)
LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, 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) {
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);
}
if (mod_ret != 0)
LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret);
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
}
@ -430,7 +386,7 @@ bar_new(const struct bar_config *config)
break;
case BAR_BACKEND_WAYLAND:
#if defined(ENABLE_WAYLAND)
#if defined(BAR_WAYLAND)
backend_data = bar_backend_wayland_new();
backend_iface = &wayland_backend_iface;
#else
@ -445,7 +401,6 @@ 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;
@ -454,10 +409,7 @@ 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.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.width = config->border.width;
priv->border.color = config->border.color;
priv->border.left_margin = config->border.left_margin;
priv->border.right_margin = config->border.right_margin;
@ -488,7 +440,6 @@ 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;

View file

@ -1,7 +1,6 @@
#pragma once
#include "../color.h"
#include "../font-shaping.h"
#include "../module.h"
struct bar {
@ -13,21 +12,16 @@ 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;
@ -36,8 +30,7 @@ struct bar_config {
pixman_color_t background;
struct {
int left_width, right_width;
int top_width, bottom_width;
int width;
pixman_color_t color;
int left_margin, right_margin;
int top_margin, bottom_margin;

View file

@ -7,11 +7,11 @@ endif
if backend_wayland
wayland_protocols = dependency('wayland-protocols')
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
wayland_protocols_datadir = wayland_protocols.get_pkgconfig_variable('pkgdatadir')
wscanner = dependency('wayland-scanner', native: true)
wscanner_prog = find_program(
wscanner.get_variable('wayland_scanner'), native: true)
wscanner.get_pkgconfig_variable('wayland_scanner'), native: true)
wl_proto_headers = []
wl_proto_src = []

View file

@ -3,11 +3,9 @@
#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;
@ -17,8 +15,7 @@ struct private
pixman_color_t background;
struct {
int left_width, right_width;
int top_width, bottom_width;
int width;
pixman_color_t color;
int left_margin, right_margin;
int top_margin, bottom_margin;

File diff suppressed because it is too large Load diff

222
bar/xcb.c
View file

@ -1,16 +1,16 @@
#include "xcb.h"
#include <assert.h>
#include <string.h>
#include <assert.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,6 +39,7 @@ struct xcb_backend {
void *client_pixmap;
size_t client_pixmap_size;
pixman_image_t *pix;
};
void *
@ -54,8 +55,11 @@ 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");
}
@ -72,8 +76,10 @@ 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));
@ -84,13 +90,17 @@ 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) {
@ -101,11 +111,14 @@ setup(struct bar *_bar)
backend->x = mon->x;
backend->y = mon->y;
bar->width = mon->width;
backend->y += bar->location == BAR_TOP ? 0 : mon->height - bar->height_with_border;
backend->y += bar->location == BAR_TOP ? 0
: screen->height_in_pixels - 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;
@ -142,47 +155,74 @@ 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 = bar->height_with_border;
top_strut = backend->y + 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 = bar->height_with_border;
bottom_strut = screen->height_in_pixels - backend->y;
bottom_pair[0] = backend->x;
bottom_pair[1] = backend->x + bar->width - 1;
@ -192,38 +232,42 @@ 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);
@ -266,8 +310,10 @@ 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;
@ -277,7 +323,10 @@ loop(struct bar *_bar, void (*expose)(const 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);
@ -286,14 +335,18 @@ loop(struct bar *_bar, void (*expose)(const 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));
@ -316,12 +369,9 @@ loop(struct bar *_bar, void (*expose)(const 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;
@ -355,9 +405,10 @@ 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);
}
@ -369,19 +420,23 @@ refresh(const struct bar *_bar)
/* Send an event to handle refresh from main thread */
/* Note: docs say that all X11 events are 32 bytes, regardless of
/* Note: docs say that all X11 events are 32 bytes, reglardless 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);
@ -403,14 +458,8 @@ 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);
}
static const char *
output_name(const struct bar *_bar)
{
/* Not implemented */
return NULL;
xcb_change_window_attributes(
backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
}
const struct backend xcb_backend_iface = {
@ -420,5 +469,4 @@ const struct backend xcb_backend_iface = {
.commit = &commit,
.refresh = &refresh,
.set_cursor = &set_cursor,
.output_name = &output_name,
};

View file

@ -1,83 +0,0 @@
#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;
}

View file

@ -1,7 +0,0 @@
#pragma once
#include <stddef.h>
#include <uchar.h>
size_t c32len(const char32_t *s);
char32_t *ambstoc32(const char *src);

View file

@ -8,6 +8,5 @@ _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]'

View file

@ -1,7 +1,7 @@
#include "config.h"
#include <assert.h>
#include <string.h>
#include <assert.h>
#include <tllist.h>
@ -16,9 +16,11 @@ 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';
@ -43,27 +45,8 @@ 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));
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));
LOG_ERR("%s: value is not an integer: '%s'",
conf_err_prefix(chain, node), yml_value_as_string(node));
return false;
}
@ -76,7 +59,10 @@ 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;
}
@ -85,7 +71,8 @@ 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) {
@ -106,7 +93,8 @@ conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *val
}
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));
@ -121,7 +109,10 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct at
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));
@ -161,43 +152,21 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct at
return true;
}
static bool
verify_on_click_path(keychain_t *chain, const struct yml_node *node)
{
if (!conf_verify_string(chain, node))
return false;
#if 1
/* We allow non-absolute paths in on-click handlers */
return true;
#else
const char *path = yml_value_as_string(node);
const bool is_absolute = path[0] == '/';
const bool is_tilde = path[0] == '~' && path[1] == '/';
if (!is_absolute && !is_tilde) {
LOG_ERR("%s: path must be either absolute, or begin with '~/", conf_err_prefix(chain, node));
return false;
}
return true;
#endif
}
bool
conf_verify_on_click(keychain_t *chain, const struct yml_node *node)
{
/* on-click: <command> */
const char *s = yml_value_as_string(node);
if (s != NULL)
return verify_on_click_path(chain, node);
return true;
static const struct attr_info info[] = {
{"left", false, &verify_on_click_path}, {"middle", false, &verify_on_click_path},
{"right", false, &verify_on_click_path}, {"wheel-up", false, &verify_on_click_path},
{"wheel-down", false, &verify_on_click_path}, {"previous", false, &verify_on_click_path},
{"next", false, &verify_on_click_path}, {NULL, false, NULL},
{"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},
};
return conf_verify_dict(chain, node, info);
@ -216,30 +185,27 @@ 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)
{
@ -247,8 +213,7 @@ 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;
}
@ -264,7 +229,8 @@ 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;
}
@ -281,7 +247,10 @@ 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;
}
@ -296,8 +265,7 @@ 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;
}
@ -313,7 +281,8 @@ 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;
}
@ -335,18 +304,19 @@ 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;
}
@ -362,7 +332,8 @@ 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;
}
@ -384,7 +355,10 @@ 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;
}
@ -396,12 +370,14 @@ static bool
verify_bar_border(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"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},
{"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},
};
return conf_verify_dict(chain, node, attrs);
@ -413,12 +389,6 @@ 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)
{
@ -431,31 +401,29 @@ conf_verify_bar(const struct yml_node *bar)
chain_push(&chain, "bar");
static const struct attr_info attrs[] = {
{"height", true, &conf_verify_unsigned},
{"height", true, &conf_verify_int},
{"location", true, &verify_bar_location},
{"background", true, &conf_verify_color},
{"monitor", false, &conf_verify_string},
{"layer", false, &verify_bar_layer},
{"spacing", false, &conf_verify_unsigned},
{"left-spacing", false, &conf_verify_unsigned},
{"right-spacing", false, &conf_verify_unsigned},
{"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_int},
{"margin", false, &conf_verify_unsigned},
{"left-margin", false, &conf_verify_unsigned},
{"right-margin", false, &conf_verify_unsigned},
{"margin", false, &conf_verify_int},
{"left_margin", false, &conf_verify_int},
{"right_margin", false, &conf_verify_int},
{"border", false, &verify_bar_border},
{"font", false, &conf_verify_font},
{"font-shaping", false, &conf_verify_font_shaping},
{"foreground", false, &conf_verify_color},
{"left", false, &verify_module_list},
{"center", false, &verify_module_list},
{"right", false, &verify_module_list},
{"trackpad-sensitivity", false, &conf_verify_unsigned},
{"trackpad-sensitivity", false, &conf_verify_int},
{NULL, false, NULL},
};

View file

@ -26,14 +26,15 @@ 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,
@ -42,7 +43,6 @@ 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);

235
config.c
View file

@ -1,10 +1,9 @@
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <dlfcn.h>
@ -21,7 +20,9 @@
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';
@ -55,9 +56,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,
};
}
@ -65,69 +66,7 @@ conf_to_color(const struct yml_node *node)
struct fcft_font *
conf_to_font(const struct yml_node *node)
{
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;
}
return fcft_from_name(1, &(const char *){yml_value_as_string(node)}, NULL);
}
struct deco *
@ -145,20 +84,25 @@ 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);
@ -167,8 +111,8 @@ particle_simple_list_from_config(const struct yml_node *node, struct conf_inheri
assert(particle_list_new != NULL);
}
struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping,
inherited.foreground, NULL);
struct particle *common = particle_common_new(
0, 0, NULL, fcft_clone(inherited.font), inherited.foreground, NULL);
return particle_list_new(common, parts, count, 0, 2);
}
@ -187,53 +131,28 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *right_margin = yml_get_value(pair.value, "right-margin");
const struct yml_node *on_click = yml_get_value(pair.value, "on-click");
const struct yml_node *font_node = yml_get_value(pair.value, "font");
const struct yml_node *font_shaping_node = yml_get_value(pair.value, "font-shaping");
const struct yml_node *foreground_node = yml_get_value(pair.value, "foreground");
const struct yml_node *deco_node = yml_get_value(pair.value, "deco");
int left = margin != NULL ? yml_value_as_int(margin) : left_margin != NULL ? yml_value_as_int(left_margin) : 0;
int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0;
int left = margin != NULL ? yml_value_as_int(margin) :
left_margin != NULL ? yml_value_as_int(left_margin) : 0;
int right = margin != NULL ? yml_value_as_int(margin) :
right_margin != NULL ? yml_value_as_int(right_margin) : 0;
char *on_click_templates[MOUSE_BTN_COUNT] = {NULL};
const char *on_click_templates[MOUSE_BTN_COUNT] = {NULL};
if (on_click != NULL) {
const char *yml_legacy = yml_value_as_string(on_click);
if (yml_legacy != NULL) {
char *legacy = NULL;
if (yml_legacy[0] == '~' && yml_legacy[1] == '/') {
const char *home_dir = getenv("HOME");
if (home_dir != NULL)
if (asprintf(&legacy, "%s/%s", home_dir, yml_legacy + 2) < 0)
legacy = NULL;
if (legacy == NULL)
legacy = strdup(yml_legacy);
} else
legacy = strdup(yml_legacy);
const char *legacy = yml_value_as_string(on_click);
if (legacy != NULL)
on_click_templates[MOUSE_BTN_LEFT] = legacy;
}
else if (yml_is_dict(on_click)) {
for (struct yml_dict_iter it = yml_dict_iter(on_click); it.key != NULL; yml_dict_next(&it)) {
if (yml_is_dict(on_click)) {
for (struct yml_dict_iter it = yml_dict_iter(on_click);
it.key != NULL;
yml_dict_next(&it))
{
const char *key = yml_value_as_string(it.key);
const char *yml_template = yml_value_as_string(it.value);
char *template = NULL;
if (yml_template[0] == '~' && yml_template[1] == '/') {
const char *home_dir = getenv("HOME");
if (home_dir != NULL)
if (asprintf(&template, "%s/%s", home_dir, yml_template + 2) < 0)
template = NULL;
if (template == NULL)
template = strdup(yml_template);
} else
template = strdup(yml_template);
const char *template = yml_value_as_string(it.value);
if (strcmp(key, "left") == 0)
on_click_templates[MOUSE_BTN_LEFT] = template;
@ -245,10 +164,6 @@ 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);
}
@ -266,14 +181,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);
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;
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;
/* Instantiate base/common particle */
struct particle *common
= particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco);
struct particle *common = particle_common_new(
left, right, on_click_templates, font, foreground, deco);
const struct particle_iface *iface = plugin_load_particle(type);
@ -289,8 +204,6 @@ 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,
};
/*
@ -301,7 +214,8 @@ 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);
@ -314,21 +228,6 @@ 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);
@ -353,16 +252,15 @@ 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");
@ -371,24 +269,16 @@ 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.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);
conf.border.width = yml_value_as_int(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);
@ -408,7 +298,6 @@ 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");
@ -417,17 +306,12 @@ 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,
};
@ -443,7 +327,10 @@ 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);
@ -454,14 +341,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_font_shaping = yml_get_value(m.value, "font-shaping");
const struct yml_node *mod_foreground = yml_get_value(m.value, "foreground");
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,
.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,
.font = mod_font != NULL
? conf_to_font(mod_font) : inherited.font,
.foreground = mod_foreground != NULL
? conf_to_color(mod_foreground) : inherited.foreground,
};
const struct module_iface *iface = plugin_load_module(mod_name);

View file

@ -1,9 +1,8 @@
#pragma once
#include "bar/bar.h"
#include "font-shaping.h"
#include "yml.h"
#include <fcft/fcft.h>
#include "yml.h"
#include "bar/bar.h"
struct bar;
struct particle;
@ -17,13 +16,12 @@ 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);

View file

@ -4,11 +4,10 @@
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}

View file

@ -1,13 +1,12 @@
#include <stdlib.h>
#include "../config-verify.h"
#include "../config.h"
#include "../config-verify.h"
#include "../decoration.h"
#include "../plugin.h"
struct private
{
// struct rgba color;
struct private {
//struct rgba color;
pixman_color_t color;
};
@ -23,7 +22,9 @@ 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 *

View file

@ -1,91 +0,0 @@
#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

View file

@ -1,7 +1,7 @@
deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
decorations = []
foreach deco : ['background', 'border', 'stack', 'underline', 'overline']
foreach deco : ['background', 'stack', 'underline']
if plugs_as_libs
shared_module('@0@'.format(deco), '@0@.c'.format(deco),
dependencies: deco_sdk,

View file

@ -1,71 +0,0 @@
#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

View file

@ -1,14 +1,13 @@
#include <stdlib.h>
#define LOG_MODULE "stack"
#include "../config-verify.h"
#include "../config.h"
#include "../decoration.h"
#include "../log.h"
#include "../config.h"
#include "../config-verify.h"
#include "../decoration.h"
#include "../plugin.h"
struct private
{
struct private {
struct deco **decos;
size_t count;
};
@ -58,7 +57,10 @@ 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);
}
@ -73,7 +75,10 @@ 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;
}

View file

@ -1,12 +1,11 @@
#include <stdlib.h>
#include "../config-verify.h"
#include "../config.h"
#include "../config-verify.h"
#include "../decoration.h"
#include "../plugin.h"
struct private
{
struct private {
int size;
pixman_color_t color;
};
@ -23,8 +22,9 @@ 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_unsigned},
{"size", true, &conf_verify_int},
{"color", true, &conf_verify_color},
DECORATION_COMMON_ATTRS,
};

View file

@ -1,86 +1,18 @@
sh = find_program('sh', native: true)
scdoc = dependency('scdoc', native: true)
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
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
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']
parts = man_src.split('.')
name = parts[-3]
section = parts[-2]
@ -90,7 +22,7 @@ foreach man_src : ['yambar.1.scd',
out,
output: out,
input: man_src,
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
capture: true,
install: true,
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))

View file

@ -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,74 +70,9 @@ 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 particle combines multiple decorations.
This particles combines multiple decorations.
## CONFIGURATION

View file

@ -7,21 +7,13 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
[[ *Name*
:[ *Type*
:< *Description*
| online
: bool
: True when the ALSA device has successfully been opened
| dB
: range
: Volume level (in dB), with min and max as start and end range
values.
:[ *Description*
| volume
: range
: Volume level (raw), with min and max as start and end range values
: Volume level, with min and max as start and end range values
| percent
: range
: Volume level, as a percentage. This value is based on the *dB* tag
if available, otherwise the *volume* tag.
: Volume level, as a percentage
| muted
: bool
: True if muted, otherwise false
@ -32,7 +24,7 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| card
: string
: yes
@ -41,17 +33,6 @@ 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

View file

@ -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

View file

@ -8,22 +8,11 @@ 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
@ -49,7 +38,7 @@ the state *unknown* under other conditions.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| name
: string
: yes
@ -57,20 +46,7 @@ the state *unknown* under other conditions.
| poll-interval
: int
: no
: How often, in milliseconds, to poll for capacity changes
(default=*60000*). Set to `0` to disable polling (*warning*: many
batteries do not support asynchronous reporting). Cannot be less
than 250ms.
| battery-scale
: int
: no
: How much to scale down the battery charge amount. Some batteries
report too high resulting in bad discharge estimates. Default=1.
| smoothing-secs
: int
: no
: How many seconds to perform smoothing over for battery discharge
estimates. Default=100s.
: 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).
# EXAMPLES
@ -79,7 +55,7 @@ bar:
left:
- battery:
name: BAT0
poll-interval: 30000
poll-interval: 30
content:
string: {text: "BAT: {capacity}% {estimate}"}
```

View file

@ -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,10 +29,6 @@ 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

View file

@ -1,79 +0,0 @@
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)

View file

@ -1,85 +0,0 @@
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)

View file

@ -1,107 +0,0 @@
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)

View file

@ -1,78 +0,0 @@
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)

View file

@ -22,13 +22,10 @@ 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)
@ -38,9 +35,6 @@ 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:
@ -60,7 +54,7 @@ with the _application_ and _title_ tags to replace the X11-only
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| content
: associative array
: yes
@ -70,15 +64,7 @@ with the _application_ and _title_ tags to replace the X11-only
| sort
: enum
: no
: How to sort the list of workspaces; one of _none_, _native_, _ascending_ or _descending_, defaults to _none_. Use _native_ to sort numbered workspaces only.
| strip-workspace-numbers
: bool
: no
: If true, *N:* prefixes will be stripped from workspace names. Useful together with *sort*, to have the workspace order fixed.
| persistent
: list of strings
: no
: Persistent workspaces. I.e. workspaces that are never removed, even if empty.
: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
| left-spacing
: int
: no
@ -105,9 +91,10 @@ bar:
content:
"":
map:
tag: state
default: {string: {text: "{name}"}}
conditions:
state == focused: {string: {text: "{name}*"}}
values:
focused: {string: {text: "{name}*"}}
current: { string: {text: "{application}: {title}"}}
```

View file

@ -1,52 +0,0 @@
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)

View file

@ -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,9 +20,6 @@ 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
@ -35,9 +32,6 @@ 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
@ -59,7 +53,7 @@ mpd - This module provides MPD status such as currently playing artist/album/son
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| host
: string
: yes

View file

@ -1,101 +0,0 @@
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)

View file

@ -6,31 +6,20 @@ network - This module monitors network connection state
# DESCRIPTION
This module monitors network connection state; disconnected/connected
state and MAC/IP addresses. It instantiates the provided _content_
particle for each network interface.
state and MAC/IP addresses.
Note: while the module internally tracks all assigned IPv4/IPv6
addresses, it currently exposes only a single IPv4 and a single IPv6
address per network interface.
address.
# 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
@ -50,74 +39,26 @@ address per network interface.
| ipv6
: string
: IPv6 address assigned to the interface, or *""* if none
| ssid
: string
: SSID the adapter is connected to (Wi-Fi only)
| signal
: int
: Signal strength, in dBm (Wi-Fi only)
| quality
: range
: Quality of the signal, in percent (Wi-Fi only)
| rx-bitrate
: int
: RX bitrate, in bits/s
| tx-bitrate
: int
: TX bitrate in bits/s
| dl-speed
: int
: Download speed in bits/s
| ul-speed
: int
: Upload speed in bits/s
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| left-spacing
: int
: no
: Space, in pixels, in the left side of each rendered volume
| right-spacing
: int
: no
: Space, in pixels, on the right side of each rendered volume
| spacing
: int
: no
: Short-hand for setting both _left-spacing_ and _right-spacing_
| poll-interval
: int
: no
: Periodically (in milliseconds) update the signal, quality, rx+tx bitrate, and
ul+dl speed tags (default=0). Setting it to 0 disables updates. Cannot be less
than 250ms.
:[ *Description*
| name
: string
: Name of network interface to monitor
# EXAMPLES
Display all Ethernet (including WLAN) devices. This excludes loopback,
bridges etc.
```
bar:
left:
- network:
name: wlp3s0
content:
map:
conditions:
type == ether || type == wlan:
map:
default:
string: {text: "{name}: {state} ({ipv4})"}
conditions:
ipv4 == "":
string: {text: "{name}: {state}"}
string: {text: "{name}: {state} ({ipv4})"}
```
# SEE ALSO

View file

@ -1,34 +0,0 @@
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)

View file

@ -1,60 +0,0 @@
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)

View file

@ -1,103 +0,0 @@
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)

View file

@ -1,67 +0,0 @@
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)

View file

@ -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,10 +22,6 @@ 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?*)
@ -48,7 +44,7 @@ instantiates the provided _content_ particle for each detected drive.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| left-spacing
: int
: no
@ -74,12 +70,13 @@ bar:
- removables:
content:
map:
conditions:
~mounted:
tag: mounted
values:
false:
string:
on-click: udisksctl mount -b {device}
text: "{label}"
mounted:
true:
string:
on-click: udisksctl unmount -b {device}
text: "{label}"

View file

@ -1,7 +1,7 @@
yambar-modules-river(5)
# NAME
river - This module provides information about the river tags
river - This module provide information about the river tags
# DESCRIPTION
@ -12,25 +12,21 @@ about the river tags.
It has an interface similar to the i3/sway module.
The configuration for the river module specifies one _title_ particle,
which will be instantiated once for each seat, with tags representing
the seats' name, the title of the seats' currently focused view, and
its current river "mode".
which will be instantiated with tags representing the currently active
seat and the currently focused view's title.
It also specifies a _content_ template particle, which is instantiated
once for all 32 river tags. This means you probably want to use a
*map* particle to hide unused river tags.
# TAGS (for the "content" particle)
# TAGS
[[ *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).
@ -42,35 +38,20 @@ 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 *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*
: 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.
| seat
: string
: The name of the seat.
: The name of the currently active seat (*title* particle only, see CONFIGURATION)
| title
: string
: The seat's focused view's title.
| mode
: string
: The seat's current mode (entered with e.g. *riverctl enter-mode foobar*).
| layout
: string
: Current layout of the output currently focused by the seat.
: The focused view's title (*title* particle only, see CONFIGURATION)
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| title
: particle
: no
@ -79,12 +60,6 @@ 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
@ -92,12 +67,13 @@ once for all 32 river tags. This means you probably want to use a
bar:
left:
- river:
title: {string: { text: "{seat} - {title} ({layout}/{mode})" }}
title: {string: { text: "{seat} - {title}" }}
content:
map:
conditions:
~occupied: {empty: {}}
occupied:
tag: occupied
values:
false: {empty: {}}
true:
string:
margin: 5
text: "{id}: {state}"

View file

@ -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 depend
is received. This mode is intended to be used by scripts that depends
on non-polling methods to update their state.
Tag sets, or _transactions_, are separated by an empty line
@ -66,21 +66,19 @@ User defined.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| path
: string
: yes
: Path to script/binary to execute. Must either be an absolute path,
or start with *~/*.
: Path to script/binary to execute. Must be an absolute path.
| args
: list of strings
: no
: Arguments to pass to the script/binary.
| poll-interval
: integer
: no
: Number of milliseconds between each script run. If unset, or set to
0, continuous mode is used.
: Number of seconds between each script run. If unset, continuous mode
is used.
# EXAMPLES
@ -115,36 +113,6 @@ 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)

View file

@ -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

View file

@ -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)")

View file

@ -16,7 +16,7 @@ _title_ tags.
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| application
: string
: Name of the application that owns the currently focused window

View file

@ -38,7 +38,7 @@ For example, to render _backlight_ as " 20%", you could use:
```
content:
- string:
font: Font Awesome 6 Free:style=solid:pixelsize=14
font: Font Awesome 5 Free:style=solid:pixelsize=14
text: 
- string:
font: Adobe Helvetica:pixelsize=12
@ -68,17 +68,20 @@ in red.
```
content:
map:
conditions:
~carrier: {empty: {}}
carrier:
tag: carrier
values:
false: {empty: {}}
true:
map:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}}
conditions:
state == up:
values:
up:
map:
tag: ipv4
default: {string: {text: , font: *awesome}}
conditions:
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
values:
"": {string: {text: , font: *awesome, foreground: ffffff66}}
```
## Use yaml anchors
@ -91,7 +94,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 6 Free:style=solid:pixelsize=14
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
```
@ -110,7 +113,7 @@ following attributes are supported by all modules:
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| content
: particle
: yes
@ -142,28 +145,14 @@ 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)
@ -174,10 +163,6 @@ 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)

View file

@ -12,7 +12,7 @@ following attributes are supported by all particles:
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| left-margin
: int
: no
@ -31,76 +31,20 @@ 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 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.
: Command to execute when the particle is clicked. Tags can be
used. Note that the string is *not* executed in a shell.
| 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
@ -115,7 +59,7 @@ of free text mixed with tag specifiers.
| text
: string
: yes
: Format string. Tags are specified with _{tag_name}_. Some tag types
: Format string. Tags are spcified with _{tag_name}_. Some tag types
have suffixes that can be appended (e.g. _{tag_name:suffix}_). See
*yambar-modules*(5)).
| max
@ -123,9 +67,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 "…" is
"..." will be appended. Note that the trailing "..." are
*included* in the maximum length. I.e. if you set _max_ to '5', you
will only get *4* characters from the string.
will only get *2* characters from the string.
## EXAMPLES
@ -138,7 +82,7 @@ content:
# EMPTY
This particle is a place-holder. While it does not render any tags,
margins and decorations are rendered.
margins and decortions are rendered.
## CONFIGURATION
@ -155,7 +99,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 any other particle
different font and/or color formatting. Or ay other particle
combinations.
But note that this means you *cannot* set any attributes on the _list_
@ -166,7 +110,7 @@ particle itself.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| items
: list
: yes
@ -214,165 +158,51 @@ 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 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
particles. 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*
| conditions
:[ *Description*
| tag
: string
: yes
: The tag (name of) which values should be mapped
| values
: associative array
: yes
: An associative array of conditions (see above) mapped to particles
: An associative array of tag values mapped to particles
| default
: particle
: no
: Default particle to use, none of the conditions are true
: Default particle to use, when tag's value does not match any of the
mapped values.
## EXAMPLES
```
content:
map:
tag: tag_name
default:
string:
text: this is the default particle; the tag's value is now {tag_name}
conditions:
tag == one_value:
values:
one_value:
string:
text: tag's value is now one_value
tag == another_value:
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
@ -385,7 +215,7 @@ indicator.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| tag
: string
: yes
@ -396,18 +226,6 @@ 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
@ -440,7 +258,7 @@ itself when needed.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| tag
: string
: yes
@ -476,7 +294,7 @@ itself when needed.
```
content:
progress-bar:
progres-bar:
tag: tag_name
length: 20
start: {string: {text: ├}}

View file

@ -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,97 +38,9 @@ The available tag *types* are:
# FORMATTING
A tag may be followed by one or more formatters that alter the tags
rendition.
As mentioned above, each tag type has a default representation that is
used when the tag is rendered by a string particle.
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
```
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}\"_.

View file

@ -25,11 +25,7 @@ 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 upon exiting.
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as
syslog. Default: _warning_.
as been specified, the file is unlinked exit.
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
Enables or disables colorization of log output on stderr.

View file

@ -12,10 +12,9 @@ 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 comma separated list of fonts in _fontconfig_
format. Example of valid values:
- Font Awesome 6 Brands
- Font Awesome 6 Free:style=solid
- *font*: this is a string in _fontconfig_ format. Example of valid values:
- Font Awesome 5 Brands
- Font Awesome 5 Free:style=solid
- Dina:pixelsize=10:slant=italic
- Dina:pixelsize=10:weight=bold
- *color*: an rgba hexstring; _RRGGBBAA_. Examples:
@ -23,17 +22,12 @@ 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
@ -51,11 +45,6 @@ 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
@ -84,26 +73,10 @@ 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
: Short-hand for setting _border.left/right/top/bottom-width_
: Width, in pixels, of the border
| border.color
: color
: no
@ -131,17 +104,7 @@ types that are frequently used:
| font
: font
: no
: 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.
: Default font to use in modules and particles
| foreground
: color
: no
@ -178,8 +141,8 @@ bar:
right:
- clock:
content:
- string: {text: "{time}"}
content:
- string: {text: "{time}"}
```
# FILES

View file

@ -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 reusing here and there
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14
awesome_brands: &awesome_brands Font Awesome 6 Brands:pixelsize=16
# fonts we'll be re-using here and there
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
awesome_brands: &awesome_brands Font Awesome 5 Brands:pixelsize=16
std_underline: &std_underline {underline: { size: 2, color: ff0000ff}}
@ -46,67 +46,82 @@ 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}
conditions:
mode == default: {empty: {}}
values:
default: {empty: {}}
content:
"":
map:
conditions:
state == focused: {string: {<<: [*default, *focused]}}
state == unfocused: {string: {<<: *default}}
state == invisible: {string: {<<: [*default, *invisible]}}
state == urgent: {string: {<<: [*default, *urgent]}}
tag: state
values:
focused: {string: {<<: [*default, *focused]}}
unfocused: {string: {<<: *default}}
invisible: {string: {<<: [*default, *invisible]}}
urgent: {string: {<<: [*default, *urgent]}}
main:
map:
conditions:
state == focused: {string: {<<: [*main, *focused]}}
state == unfocused: {string: {<<: *main}}
state == invisible: {string: {<<: [*main, *invisible]}}
state == urgent: {string: {<<: [*main, *urgent]}}
tag: state
values:
focused: {string: {<<: [*main, *focused]}}
unfocused: {string: {<<: *main}}
invisible: {string: {<<: [*main, *invisible]}}
urgent: {string: {<<: [*main, *urgent]}}
surfing:
map:
conditions:
state == focused: {string: {<<: [*surfing, *focused]}}
state == unfocused: {string: {<<: *surfing}}
state == invisible: {string: {<<: [*surfing, *invisible]}}
state == urgent: {string: {<<: [*surfing, *urgent]}}
tag: state
values:
focused: {string: {<<: [*surfing, *focused]}}
unfocused: {string: {<<: *surfing}}
invisible: {string: {<<: [*surfing, *invisible]}}
urgent: {string: {<<: [*surfing, *urgent]}}
misc:
map:
conditions:
state == focused: {string: {<<: [*misc, *focused]}}
state == unfocused: {string: {<<: *misc}}
state == invisible: {string: {<<: [*misc, *invisible]}}
state == urgent: {string: {<<: [*misc, *urgent]}}
tag: state
values:
focused: {string: {<<: [*misc, *focused]}}
unfocused: {string: {<<: *misc}}
invisible: {string: {<<: [*misc, *invisible]}}
urgent: {string: {<<: [*misc, *urgent]}}
mail:
map:
conditions:
state == focused: {string: {<<: [*mail, *focused]}}
state == unfocused: {string: {<<: *mail}}
state == invisible: {string: {<<: [*mail, *invisible]}}
state == urgent: {string: {<<: [*mail, *urgent]}}
tag: state
values:
focused: {string: {<<: [*mail, *focused]}}
unfocused: {string: {<<: *mail}}
invisible: {string: {<<: [*mail, *invisible]}}
urgent: {string: {<<: [*mail, *urgent]}}
music:
map:
conditions:
state == focused: {string: {<<: [*music, *focused]}}
state == unfocused: {string: {<<: *music}}
state == invisible: {string: {<<: [*music, *invisible]}}
state == urgent: {string: {<<: [*music, *urgent]}}
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}
- foreign-toplevel:
content:
map:
conditions:
~activated: {empty: {}}
activated:
- string: {text: "{app-id}", foreground: ffa0a0ff}
- string: {text: ": {title}"}
center:
- mpd:
host: /run/mpd/socket
@ -115,28 +130,32 @@ bar:
spacing: 0
items:
- map:
conditions:
state == playing: {string: {text: "{artist}"}}
state == paused: {string: {text: "{artist}", foreground: ffffff66}}
tag: state
values:
playing: {string: {text: "{artist}"}}
paused: {string: {text: "{artist}", foreground: ffffff66}}
- string: {text: " | ", foreground: ffffff66}
- map:
conditions:
state == playing: {string: {text: "{album}"}}
state == paused: {string: {text: "{album}", foreground: ffffff66}}
tag: state
values:
playing: {string: {text: "{album}"}}
paused: {string: {text: "{album}", foreground: ffffff66}}
- string: {text: " | ", foreground: ffffff66}
- map:
conditions:
state == playing: {string: {text: "{title}", foreground: ffa0a0ff}}
state == paused: {string: {text: "{title}", foreground: ffffff66}}
tag: state
values:
playing: {string: {text: "{title}", foreground: ffa0a0ff}}
paused: {string: {text: "{title}", foreground: ffffff66}}
content:
map:
margin: 10
conditions:
state == offline: {string: {text: offline, foreground: ff0000ff}}
state == stopped: {string: {text: stopped}}
state == paused: {list: *artist_album_title}
state == playing: {list: *artist_album_title}
tag: state
values:
offline: {string: {text: offline, foreground: ff0000ff}}
stopped: {string: {text: stopped}}
paused: {list: *artist_album_title}
playing: {list: *artist_album_title}
right:
- removables:
@ -146,21 +165,24 @@ bar:
spacing: 5
content:
map:
conditions:
~mounted:
tag: mounted
values:
false:
map:
tag: optical
on-click: udisksctl mount -b {device}
conditions:
~optical: [{string: *drive}, {string: {text: "{label}"}}]
optical: [{string: *optical}, {string: {text: "{label}"}}]
mounted:
values:
false: [{string: *drive}, {string: {text: "{label}"}}]
true: [{string: *optical}, {string: {text: "{label}"}}]
true:
map:
tag: optical
on-click: udisksctl unmount -b {device}
conditions:
~optical:
values:
false:
- string: {<<: *drive, deco: *std_underline}
- string: {text: "{label}"}
optical:
true:
- string: {<<: *optical, deco: *std_underline}
- string: {text: "{label}"}
- sway-xkb:
@ -169,69 +191,66 @@ bar:
- string: {text: , font: *awesome}
- string: {text: "{layout}"}
- network:
name: enp1s0
content:
map:
default: {empty: {}}
conditions:
name == enp1s0:
tag: carrier
values:
false: {empty: {}}
true:
map:
conditions:
~carrier: {empty: {}}
carrier:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}}
values:
up:
map:
default: {string: {text: , font: *awesome, foreground: ffffff66}}
conditions:
state == up && ipv4 != "": {string: {text: , font: *awesome}}
tag: ipv4
default: {string: {text: , font: *awesome}}
values:
"": {string: {text: , font: *awesome, foreground: ffffff66}}
- network:
poll-interval: 1000
name: wlp2s0
content:
map:
default: {empty: {}}
conditions:
name == wlp2s0:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}}
values:
down: {string: {text: , font: *awesome, foreground: ff0000ff}}
up:
map:
default: {string: {text: , font: *awesome, foreground: ffffff66}}
conditions:
state == down: {string: {text: , font: *awesome, foreground: ff0000ff}}
state == up:
map:
default:
- string: {text: , font: *awesome}
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s"}
conditions:
ipv4 == "":
- string: {text: , font: *awesome, foreground: ffffff66}
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s", foreground: ffffff66}
tag: ipv4
default: {string: {text: , font: *awesome}}
values:
"": {string: {text: , font: *awesome, foreground: ffffff66}}
- alsa:
card: hw:PCH
mixer: Master
content:
map:
conditions:
~online: {string: {text: , font: *awesome, foreground: ff0000ff}}
online:
map:
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
conditions:
muted: {string: {text: , font: *awesome, foreground: ffffff66}}
~muted:
ramp:
tag: percent
items:
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
tag: muted
values:
true: {string: {text: , font: *awesome, foreground: ffffff66}}
false:
ramp:
tag: volume
items:
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- backlight:
name: intel_backlight
content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}]
- battery:
name: BAT0
poll-interval: 30000
anchors:
discharging: &discharging
list:
items:
poll-interval: 30
content:
map:
tag: state
values:
discharging:
- ramp:
tag: capacity
items:
@ -246,20 +265,13 @@ bar:
- string: {text: , font: *awesome}
- string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% {estimate}"}
content:
map:
conditions:
state == unknown:
<<: *discharging
state == discharging:
<<: *discharging
state == charging:
charging:
- string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% {estimate}"}
state == full:
full:
- string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% full"}
state == "not charging":
not charging:
- ramp:
tag: capacity
items:
@ -284,6 +296,6 @@ bar:
- label:
content:
string:
on-click: systemctl poweroff
on-click: loginctl poweroff
text: 
font: *awesome

View file

@ -1,58 +0,0 @@
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}}

View file

@ -1,69 +0,0 @@
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}"}

View file

@ -19,7 +19,7 @@
#
# Now the fun part
#
# Example configuration:
# Exemple configuration:
#
# - script:
# path: /absolute/path/to/dwl-tags.sh
@ -31,33 +31,39 @@
# content:
# - map:
# margin: 4
# conditions:
# tag_0_occupied:
# tag: tag_0_occupied
# values:
# true:
# map:
# conditions:
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}}
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *occupied}}
# ~tag_0_occupied:
# tag: tag_0_focused
# values:
# true: {string: {text: "{tag_0}", <<: *focused}}
# false: {string: {text: "{tag_0}", <<: *occupied}}
# false:
# map:
# conditions:
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}}
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *default}}
# tag: tag_0_focused
# values:
# true: {string: {text: "{tag_0}", <<: *focused}}
# false: {string: {text: "{tag_0}", <<: *default}}
# ...
# ...
# ...
# - map:
# margin: 4
# conditions:
# tag_8_occupied:
# map:
# conditions:
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *occupied}}
# ~tag_8_occupied:
# tag: tag_8_occupied
# values:
# true:
# map:
# tag: tag_8_focused
# values:
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *default}}
# true: {string: {text: "{tag_8}", <<: *focused}}
# false: {string: {text: "{tag_8}", <<: *occupied}}
# false:
# map:
# tag: tag_8_focused
# values:
# true: {string: {text: "{tag_8}", <<: *focused}}
# false: {string: {text: "{tag_8}", <<: *default}}
# - list:
# spacing: 3
# items:
@ -121,7 +127,7 @@ while true; do
inotifywait -qq --event modify "${fname}"
# Get info from the file
output="$(tail -n6 "${fname}")"
output="$(tail -n4 "${fname}")"
title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )"
#selmon="$(echo "${output}" | grep 'selmon')"
layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )"
@ -136,3 +142,4 @@ done
unset -v output title layout activetags selectedtags
unset -v tags name

View file

@ -12,7 +12,7 @@
# {aur} int number of aur packages
# {pkg} int sum of both
#
# Examples configuration:
# Exemples configuration:
# - script:
# path: /absolute/path/to/pacman.sh
# args: []
@ -24,9 +24,10 @@
# args: []
# content:
# map:
# tag: pkg
# default: { string: { text: "{pacman} + {aur} = {pkg}" } }
# conditions:
# pkg == 0: {string: {text: no updates}}
# values:
# 0: {string: {text: no updates}}
declare interval aur_helper pacman_num aur_num pkg_num
@ -76,3 +77,4 @@ done
unset -v interval aur_helper pacman_num aur_num pkg_num
unset -f _err

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_status_unstable_v1">
<copyright>
Copyright 2020 The River Developers
Copyright 2020 Isaac Freund
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="4">
<interface name="zriver_status_manager_v1" version="1">
<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="4">
<interface name="zriver_output_status_v1" version="1">
<description summary="track output tags and focus">
This interface allows clients to receive information about the current
windowing state of an output.
@ -75,36 +75,12 @@
</description>
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
</event>
<event name="urgent_tags" since="2">
<description summary="tags of the output with an urgent view">
Sent once on binding the interface and again whenever the set of
tags with at least one urgent view changes.
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
<event name="layout_name" since="4">
<description summary="name of the layout">
Sent once on binding the interface should a layout name exist and again
whenever the name changes.
</description>
<arg name="name" type="string" summary="layout name"/>
</event>
<event name="layout_name_clear" since="4">
<description summary="name of the layout">
Sent when the current layout name has been removed without a new one
being set, for example when the active layout generator disconnects.
</description>
</event>
</interface>
<interface name="zriver_seat_status_v1" version="3">
<interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent
if the client has bound the relevant wl_output globals.
focus of a seat.
</description>
<request name="destroy" type="destructor">
@ -136,13 +112,5 @@
</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>

View file

@ -1,270 +0,0 @@
<?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>

View file

@ -1,7 +0,0 @@
#pragma once
enum font_shaping {
FONT_SHAPE_NONE,
FONT_SHAPE_GRAPHEMES,
FONT_SHAPE_FULL,
};

View file

@ -13,18 +13,11 @@ out_file=${3}
if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then
workdir=$(pwd)
cd "${src_dir}"
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_version=$(git describe --always --tags)
git_branch=$(git rev-parse --abbrev-ref HEAD)
cd "${workdir}"
new_version="${git_version} ($(date "+%b %d %Y"), branch '${git_branch}')"
new_version="${git_version} ($(env LC_TIME=C date "+%b %d %Y"), branch '${git_branch}')"
else
new_version="${default_version}"
fi

278
log.c
View file

@ -1,60 +1,41 @@
#include "log.h"
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#define ALEN(v) (sizeof(v) / sizeof((v)[0]))
#define UNUSED __attribute__((unused))
#include <syslog.h>
static bool colorize = false;
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},
};
static bool do_syslog = true;
void
log_init(enum log_colorize _colorize, bool _do_syslog, enum log_facility syslog_facility, enum log_class _log_level)
log_init(enum log_colorize _colorize, bool _do_syslog,
enum log_facility syslog_facility, enum log_class syslog_level)
{
static const int facility_map[] = {
[LOG_FACILITY_USER] = LOG_USER,
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
};
/* 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';
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,
};
colorize = _colorize == LOG_COLORIZE_NEVER
? false
: _colorize == LOG_COLORIZE_ALWAYS
? true
: !no_color && isatty(STDERR_FILENO);
colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO);
do_syslog = _do_syslog;
log_level = _log_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));
if (do_syslog) {
openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]);
setlogmask(LOG_UPTO(level_map[syslog_level]));
}
}
@ -66,153 +47,120 @@ 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)
{
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;
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;
}
char clr[16];
snprintf(clr, sizeof(clr), "\033[%um", class_clr);
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", prefix, colorize ? "\033[0m" : "");
snprintf(clr, sizeof(clr), "\e[%dm", class_clr);
fprintf(stderr, "%s%s%s: ", colorize ? clr : "", class, colorize ? "\e[0m" : "");
if (colorize)
fputs("\033[2m", stderr);
fprintf(stderr, "\e[2m");
fprintf(stderr, "%s:%d: ", file, lineno);
if (colorize)
fputs("\033[0m", stderr);
fprintf(stderr, "\e[0m");
vfprintf(stderr, fmt, va);
if (sys_errno != 0)
fprintf(stderr, ": %s (%d)", strerror(sys_errno), sys_errno);
fprintf(stderr, ": %s", strerror(sys_errno));
fputc('\n', stderr);
fprintf(stderr, "\n");
}
static void
_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)
_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)
{
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 = log_level_map[log_class].syslog_equivalent;
char msg[4096];
int n = vsnprintf(msg, sizeof(msg), fmt, va);
assert(n >= 0);
if (sys_errno != 0 && (size_t)n < sizeof(msg))
snprintf(msg + n, sizeof(msg) - n, ": %s", strerror(sys_errno));
syslog(level, "%s: %s", module, msg);
}
void
log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
{
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_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
log_msg_va(log_class, module, file, lineno, fmt, va);
va_end(va);
}
void
log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va)
{
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);
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;
}
return buf;
assert(level != -1);
const char *sys_err = sys_errno != 0 ? strerror(sys_errno) : NULL;
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);
}
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, 0, ap1);
_sys_log(log_class, module, file, lineno, fmt, 0, ap2);
va_end(ap1);
va_end(ap2);
}
void log_errno(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);
}
void log_errno_provided(enum log_class log_class, const char *module,
const char *file, int lineno, int _errno,
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);
}

55
log.h
View file

@ -1,43 +1,42 @@
#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 };
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_init(enum log_colorize colorize, bool do_syslog,
enum log_facility syslog_facility, enum log_class syslog_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)));
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__)
#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__)
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
#define LOG_DBG(...) log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_DBG(fmt, ...) \
log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__)
#else
#define LOG_DBG(...)
#define LOG_DBG(fmt, ...)
#endif

85
main.c
View file

@ -1,6 +1,4 @@
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <locale.h>
#include <poll.h>
#include <signal.h>
@ -11,12 +9,14 @@
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/eventfd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <pwd.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, "re");
FILE *conf_file = fopen(config_path, "r");
if (conf_file == NULL) {
LOG_ERRNO("%s: failed to open", config_path);
return NULL;
@ -127,14 +127,13 @@ 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"
" -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");
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");
}
static bool
@ -147,8 +146,9 @@ 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,16 +177,15 @@ 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-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},
{"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},
};
bool unlink_pid_file = false;
@ -196,12 +195,11 @@ 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:d:l::svh", longopts, NULL);
int c = getopt_long(argc, argv, ":b:c:Cp:l::svh", longopts, NULL);
if (c == -1)
break;
@ -222,8 +220,9 @@ 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) && !S_ISFIFO(st.st_mode)) {
fprintf(stderr, "%s: invalid configuration file: neither a regular file nor a pipe or FIFO\n", optarg);
} else if (!S_ISREG(st.st_mode)) {
fprintf(stderr, "%s: invalid configuration file: not a regular file\n",
optarg);
return EXIT_FAILURE;
}
@ -239,16 +238,6 @@ 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;
@ -284,12 +273,14 @@ main(int argc, char *const *argv)
}
}
log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, log_level);
log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, 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);
_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);
const struct sigaction sa = {.sa_handler = &signal_handler};
sigaction(SIGINT, &sa, NULL);
@ -366,7 +357,7 @@ main(int argc, char *const *argv)
}
if (aborted)
LOG_INFO("aborted: %s (%ld)", strsignal(aborted), (long)aborted);
LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted);
done:
/* Signal abort to other threads */
@ -376,7 +367,7 @@ done:
int res;
int r = thrd_join(bar_thread, &res);
if (r != 0)
LOG_ERRNO_P(r, "failed to join bar thread");
LOG_ERRNO_P("failed to join bar thread", r);
bar->destroy(bar);
close(abort_fd);

View file

@ -1,28 +1,20 @@
project('yambar', 'c',
version: '1.11.0',
version: '1.6.2',
license: 'MIT',
meson_version: '>=0.60.0',
meson_version: '>=0.53.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.global_build_root().split('/')
build_root = meson.build_root().split('/')
relative_dir_parts = []
i = 0
in_prefix = true
@ -51,9 +43,7 @@ endif
# Common dependencies
dl = cc.find_library('dl')
m = cc.find_library('m')
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
libepoll = dependency('epoll-shim', required: false)
libinotify = dependency('libinotify', required: false)
threads = dependency('threads')
pixman = dependency('pixman-1')
yaml = dependency('yaml-0.1')
@ -75,10 +65,9 @@ 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: ['>=3.0.0', '<4.0.0'], fallback: 'fcft')
fcft = dependency('fcft', version: ['>=2.4.0', '<3.0.0'], fallback: 'fcft')
add_project_arguments(
cc_flags +
['-D_GNU_SOURCE'] +
(is_debug_build ? ['-D_DEBUG'] : []) +
(backend_x11 ? ['-DENABLE_X11'] : []) +
@ -95,37 +84,30 @@ if backend_x11
c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [],
pic: plugs_as_libs)
xcb_stuff = declare_dependency(
link_with: xcb_stuff_lib,
dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr,
xcb_render, xcb_errors],
)
xcb_stuff = declare_dependency(link_with: xcb_stuff_lib)
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: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
command: [generate_version_sh, meson.project_version(), '@SOURCE_ROOT@', '@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',
@ -134,7 +116,7 @@ yambar = executable(
'tag.c', 'tag.h',
'yml.c', 'yml.h',
version,
dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] +
dependencies: [bar, pixman, yaml, threads, dl, tllist, fcft] +
decorations + particles + modules,
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
export_dynamic: true,
@ -170,34 +152,3 @@ 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
)

View file

@ -5,52 +5,3 @@ 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')

View file

@ -1,6 +1,6 @@
#include "module.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
struct module *

View file

@ -27,7 +27,7 @@ struct module {
* specified number of milliseconds */
bool (*refresh_in)(struct module *mod, long milli_seconds);
const char *(*description)(const struct module *mod);
const char *(*description)(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}

View file

@ -1,8 +1,6 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/time.h>
#include <math.h>
#include <alsa/asoundlib.h>
@ -10,86 +8,42 @@
#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"
enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE };
struct channel {
snd_mixer_selem_channel_id_t id;
enum channel_type type;
char *name;
bool use_db;
long vol_cur;
long db_cur;
bool muted;
};
struct private
{
struct private {
char *card;
char *mixer;
char *volume_name;
char *muted_name;
struct particle *label;
tll(struct channel) channels;
tll(snd_mixer_selem_channel_id_t) 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;
long vol_min;
long vol_max;
long vol_cur;
bool muted;
};
static void
channel_free(struct channel *chan)
{
free(chan->name);
}
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
tll_foreach(m->channels, it)
{
channel_free(&it->item);
tll_remove(m->channels, it);
}
tll_free(m->channels);
m->label->destroy(m->label);
free(m->card);
free(m->mixer);
free(m->volume_name);
free(m->muted_name);
free(m);
module_default_destroy(mod);
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
static char desc[32];
const struct private *m = mod->private;
struct private *m = mod->private;
snprintf(desc, sizeof(desc), "alsa(%s)", m->card);
return desc;
}
@ -99,60 +53,18 @@ 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_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, "volume", m->vol_cur, m->vol_min, m->vol_max),
tag_new_int_range(mod, "percent", percent, 0, 100),
tag_new_bool(mod, "muted", muted),
tag_new_bool(mod, "muted", m->muted),
},
.count = 5,
.count = 3,
};
mtx_unlock(&mod->lock);
@ -167,137 +79,136 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
{
struct private *m = mod->private;
mtx_lock(&mod->lock);
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));
/* If volume level can be changed (i.e. this isn't just a switch;
* 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);
* 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]);
if (r < 0) {
LOG_ERR("%s,%s: %s: failed to get current dB", m->card, m->mixer, chan->name);
LOG_WARN("%s,%s: %s: failed to get current volume",
m->card, m->mixer,
snd_mixer_selem_channel_name(it->item));
}
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;
}
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;
}
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_ERR("%s,%s: %s: failed to get current volume", 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->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;
}
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);
}
/* Get channels muted state */
tll_foreach(m->channels, it)
{
struct channel *chan = &it->item;
int unmuted[tll_length(m->channels)];
memset(unmuted, 0, sizeof(unmuted));
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);
/* Get muted state */
idx = 0;
tll_foreach(m->channels, it) {
int r = snd_mixer_selem_get_playback_switch(
elem, it->item, &unmuted[idx]);
if (r < 0) {
LOG_WARN("%s,%s: %s: failed to get muted state", m->card, m->mixer, chan->name);
unmuted = 1;
LOG_WARN("%s,%s: %s: failed to get muted state",
m->card, m->mixer, snd_mixer_selem_channel_name(it->item));
unmuted[idx] = 1;
}
chan->muted = !unmuted;
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted);
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer,
snd_mixer_selem_channel_name(it->item), !unmuted[idx]);
idx++;
}
m->online = true;
/* 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;
}
}
/* 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;
}
}
/* 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;
}
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;
}
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);
}
enum run_state {
RUN_ERROR,
RUN_FAILED_CONNECT,
RUN_DISCONNECTED,
RUN_DONE,
};
static enum run_state
run_while_online(struct module *mod)
static int
run(struct module *mod)
{
struct private *m = mod->private;
enum run_state ret = RUN_ERROR;
/* Make sure we arent still tracking channels from previous connects */
tll_free(m->channels);
int ret = 1;
snd_mixer_t *handle;
if (snd_mixer_open(&handle, 0) != 0) {
LOG_ERR("failed to open handle");
return ret;
return 1;
}
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;
}
@ -306,142 +217,37 @@ run_while_online(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++) {
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 (snd_mixer_selem_has_playback_channel(elem, i)) {
tll_push_back(m->channels, i);
}
}
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)
{
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 ? "🔊" : "🎤");
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));
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: %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);
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" : "");
mod->bar->refresh(mod->bar);
@ -454,181 +260,36 @@ run_while_online(struct module *mod)
fds[0] = (struct pollfd){.fd = mod->abort_fd, .events = POLLIN};
snd_mixer_poll_descriptors(handle, &fds[1], fd_count);
int r = poll(fds, fd_count + 1, -1);
if (r < 0) {
if (errno == EINTR)
continue;
poll(fds, fd_count + 1, -1);
LOG_ERRNO("failed to poll");
if (fds[0].revents & POLLIN)
break;
}
if (fds[0].revents & POLLIN) {
ret = RUN_DONE;
if (fds[1].revents & POLLHUP) {
/* Don't know if this can happen */
LOG_ERR("disconnected from alsa");
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:
/*
* Weve been connected - drain the watcher
*
* We dont 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, const char *volume_channel_name, const char *muted_channel_name,
struct particle *label)
alsa_new(const char *card, const char *mixer, 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;
@ -644,13 +305,12 @@ 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),
volume != NULL ? yml_value_as_string(volume) : NULL,
muted != NULL ? yml_value_as_string(muted) : NULL, conf_to_particle(content, inherited));
return alsa_new(
yml_value_as_string(card),
yml_value_as_string(mixer),
conf_to_particle(content, inherited));
}
static bool
@ -659,8 +319,6 @@ 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,
};

View file

@ -1,26 +1,24 @@
#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 <fcntl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libudev.h>
#define LOG_MODULE "backlight"
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h"
struct private
{
struct private {
struct particle *label;
char *device;
@ -41,7 +39,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "backlight";
}
@ -112,13 +110,13 @@ readint_from_fd(int fd)
static int
initialize(struct private *m)
{
int backlight_fd = open("/sys/class/backlight", O_RDONLY | O_CLOEXEC);
int backlight_fd = open("/sys/class/backlight", O_RDONLY);
if (backlight_fd == -1) {
LOG_ERRNO("/sys/class/backlight");
return -1;
}
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY | O_CLOEXEC);
int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY);
close(backlight_fd);
if (base_dir_fd == -1) {
@ -126,7 +124,7 @@ initialize(struct private *m)
return -1;
}
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY | O_CLOEXEC);
int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY);
if (max_fd == -1) {
LOG_ERRNO("/sys/class/backlight/%s/max_brightness", m->device);
close(base_dir_fd);
@ -136,7 +134,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 | O_CLOEXEC);
int current_fd = openat(base_dir_fd, "brightness", O_RDONLY);
close(base_dir_fd);
if (current_fd == -1) {
@ -146,7 +144,8 @@ 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;
}
@ -179,31 +178,20 @@ 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},
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
if (errno == EINTR)
continue;
poll(fds, 2, -1);
LOG_ERRNO("failed to poll");
if (fds[0].revents & POLLIN)
break;
}
if (fds[0].revents & POLLIN) {
ret = 0;
break;
}
struct udev_device *dev = udev_monitor_receive_device(mon);
if (dev == NULL)
continue;
const char *sysname = udev_device_get_sysname(dev);
bool is_us = sysname != NULL && strcmp(sysname, m->device) == 0;
bool is_us = strcmp(sysname, m->device) == 0;
udev_device_unref(dev);
if (!is_us)
@ -219,7 +207,7 @@ run(struct module *mod)
udev_unref(udev);
close(current_fd);
return ret;
return 0;
}
static struct module *
@ -244,7 +232,8 @@ 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

View file

@ -1,49 +1,31 @@
#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 <fcntl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libudev.h>
#include <tllist.h>
#define LOG_MODULE "battery"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h"
#define max(x, y) ((x) > (y) ? (x) : (y))
enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING };
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 private {
struct particle *label;
long poll_interval;
int battery_scale;
long smoothing_scale;
int poll_interval;
char *battery;
char *manufacturer;
char *model;
@ -57,64 +39,10 @@ struct private
long energy;
long power;
long charge;
struct current_state ema_current;
long 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)
{
@ -130,10 +58,10 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
static char desc[32];
const struct private *m = mod->private;
struct private *m = mod->private;
snprintf(desc, sizeof(desc), "bat(%s)", m->battery);
return desc;
}
@ -145,22 +73,20 @@ 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 || m->state == STATE_UNKNOWN);
assert(m->state == STATE_FULL ||
m->state == STATE_NOTCHARGING ||
m->state == STATE_CHARGING ||
m->state == STATE_DISCHARGING);
unsigned long hours;
unsigned long minutes;
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;
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;
double hours_as_float;
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
@ -172,14 +98,15 @@ 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->ema_current.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->current >= 0) {
unsigned long charge = m->state == STATE_CHARGING
? m->charge_full - m->charge : m->charge;
double hours_as_float;
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
hours_as_float = 0.0;
else if (m->ema_current.current > 0)
hours_as_float = (double)charge / m->ema_current.current;
else if (m->current > 0)
hours_as_float = (double)charge / m->current;
else
hours_as_float = 99.0;
@ -219,18 +146,20 @@ content(struct module *mod)
}
static const char *
readline_from_fd(int fd, size_t sz, char buf[static sz])
readline_from_fd(int fd)
{
ssize_t bytes = read(fd, buf, sz - 1);
static char buf[4096];
ssize_t sz = read(fd, buf, sizeof(buf) - 1);
lseek(fd, 0, SEEK_SET);
if (bytes < 0) {
if (sz < 0) {
LOG_WARN("failed to read from FD=%d", fd);
return NULL;
}
buf[bytes] = '\0';
for (ssize_t i = bytes - 1; i >= 0 && buf[i] == '\n'; bytes--)
buf[sz] = '\0';
for (ssize_t i = sz - 1; i >= 0 && buf[i] == '\n'; sz--)
buf[i] = '\0';
return buf;
@ -239,8 +168,7 @@ readline_from_fd(int fd, size_t sz, char buf[static sz])
static long
readint_from_fd(int fd)
{
char buf[512];
const char *s = readline_from_fd(fd, sizeof(buf), buf);
const char *s = readline_from_fd(fd);
if (s == NULL)
return 0;
@ -257,15 +185,13 @@ readint_from_fd(int fd)
static bool
initialize(struct private *m)
{
char line_buf[512];
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
if (pw_fd < 0) {
LOG_ERRNO("/sys/class/power_supply");
return false;
}
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
close(pw_fd);
if (base_dir_fd < 0) {
@ -274,31 +200,34 @@ initialize(struct private *m)
}
{
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY | O_CLOEXEC);
int fd = openat(base_dir_fd, "manufacturer", O_RDONLY);
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, sizeof(line_buf), line_buf));
m->manufacturer = strdup(readline_from_fd(fd));
close(fd);
}
}
{
int fd = openat(base_dir_fd, "model_name", O_RDONLY | O_CLOEXEC);
int fd = openat(base_dir_fd, "model_name", O_RDONLY);
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, sizeof(line_buf), line_buf));
m->model = strdup(readline_from_fd(fd));
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 | O_CLOEXEC);
int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery);
goto err;
@ -309,7 +238,7 @@ initialize(struct private *m)
}
{
int fd = openat(base_dir_fd, "energy_full", O_RDONLY | O_CLOEXEC);
int fd = openat(base_dir_fd, "energy_full", O_RDONLY);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery);
goto err;
@ -322,27 +251,28 @@ 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 | O_CLOEXEC);
int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery);
goto err;
}
m->charge_full_design = readint_from_fd(fd) / m->battery_scale;
m->charge_full_design = readint_from_fd(fd);
close(fd);
}
{
int fd = openat(base_dir_fd, "charge_full", O_RDONLY | O_CLOEXEC);
int fd = openat(base_dir_fd, "charge_full", O_RDONLY);
if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery);
goto err;
}
m->charge_full = readint_from_fd(fd) / m->battery_scale;
m->charge_full = readint_from_fd(fd);
close(fd);
}
} else {
@ -362,13 +292,13 @@ update_status(struct module *mod)
{
struct private *m = mod->private;
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
int pw_fd = open("/sys/class/power_supply", O_RDONLY);
if (pw_fd < 0) {
LOG_ERRNO("/sys/class/power_supply");
return false;
}
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY | O_CLOEXEC);
int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY);
close(pw_fd);
if (base_dir_fd < 0) {
@ -376,14 +306,14 @@ update_status(struct module *mod)
return false;
}
int status_fd = openat(base_dir_fd, "status", O_RDONLY | O_CLOEXEC);
int status_fd = openat(base_dir_fd, "status", O_RDONLY);
if (status_fd < 0) {
LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery);
close(base_dir_fd);
return false;
}
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY | O_CLOEXEC);
int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY);
if (capacity_fd < 0) {
LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery);
close(status_fd);
@ -391,12 +321,11 @@ update_status(struct module *mod)
return false;
}
int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY | O_CLOEXEC);
int power_fd = openat(base_dir_fd, "power_now", O_RDONLY | O_CLOEXEC);
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY | O_CLOEXEC);
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY | O_CLOEXEC);
int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY | O_CLOEXEC);
int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY | O_CLOEXEC);
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);
long capacity = readint_from_fd(capacity_fd);
long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1;
@ -404,14 +333,8 @@ 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;
if (charge >= -1) {
charge /= m->battery_scale;
}
char buf[512];
const char *status = readline_from_fd(status_fd, sizeof(buf), buf);
const char *status = readline_from_fd(status_fd);
if (status_fd >= 0)
close(status_fd);
@ -427,8 +350,6 @@ 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);
@ -436,7 +357,7 @@ update_status(struct module *mod)
if (status == NULL) {
LOG_WARN("failed to read battery state");
state = STATE_UNKNOWN;
state = STATE_DISCHARGING;
} else if (strcmp(status, "Full") == 0)
state = STATE_FULL;
else if (strcmp(status, "Not charging") == 0)
@ -446,32 +367,24 @@ update_status(struct module *mod)
else if (strcmp(status, "Discharging") == 0)
state = STATE_DISCHARGING;
else if (strcmp(status, "Unknown") == 0)
state = STATE_UNKNOWN;
state = STATE_DISCHARGING;
else {
LOG_ERR("unrecognized battery state: %s", status);
state = STATE_UNKNOWN;
state = STATE_DISCHARGING;
}
LOG_DBG("capacity: %ld, energy: %ld, power: %ld, charge=%ld, current=%ld, "
"time-to-empty: %ld, time-to-full: %ld",
capacity, energy, power, charge, current, time_to_empty, time_to_full);
"time-to-empty: %ld", capacity, energy, power, charge, current,
time_to_empty);
mtx_lock(&mod->lock);
if (m->state != state) {
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
}
m->state = state;
m->capacity = capacity;
m->energy = energy;
m->power = power;
m->charge = charge;
if (current != -1) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
ema_linear(&m->ema_current, (struct current_state){current, current, t}, m->smoothing_scale);
}
m->current = current;
m->time_to_empty = time_to_empty;
m->time_to_full = time_to_full;
mtx_unlock(&mod->lock);
return true;
}
@ -485,10 +398,13 @@ 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;
@ -506,82 +422,31 @@ 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},
};
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;
}
poll(fds, 2, m->poll_interval > 0 ? m->poll_interval * 1000 : -1);
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);
if (dev != NULL) {
const char *sysname = udev_device_get_sysname(dev);
udev_for_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
const char *sysname = udev_device_get_sysname(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");
bool is_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
udev_device_unref(dev);
udev_device_unref(dev);
}
if (!is_us)
continue;
}
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;
}
if (update_status(mod))
bar->refresh(bar);
}
out:
@ -593,17 +458,13 @@ out:
}
static struct module *
battery_new(const char *battery, struct particle *label, long poll_interval_msecs, int battery_scale,
long smoothing_secs)
battery_new(const char *battery, struct particle *label, int poll_interval_secs)
{
struct private *m = calloc(1, sizeof(*m));
m->label = label;
m->poll_interval = poll_interval_msecs;
m->battery_scale = battery_scale;
m->smoothing_scale = smoothing_secs * one_sec_in_ns;
m->poll_interval = poll_interval_secs;
m->battery = strdup(battery);
m->state = STATE_UNKNOWN;
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
m->state = STATE_DISCHARGING;
struct module *mod = module_common_new();
mod->private = m;
@ -620,29 +481,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 *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) : 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;
return battery_new(
yml_value_as_string(name),
conf_to_particle(c, inherited),
poll_interval != NULL ? yml_value_as_int(poll_interval) : 60);
}
static bool
@ -650,9 +493,7 @@ 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_poll_interval},
{"battery-scale", false, &conf_verify_unsigned},
{"smoothing-secs", false, &conf_verify_unsigned},
{"poll-interval", false, &conf_verify_int},
MODULE_COMMON_ATTRS,
};

View file

@ -1,22 +1,20 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <poll.h>
#include <sys/time.h>
#define LOG_MODULE "clock"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h"
struct private
{
struct private {
struct particle *label;
enum {
UPDATE_GRANULARITY_SECONDS,
@ -24,7 +22,6 @@ struct private
} update_granularity;
char *date_format;
char *time_format;
bool utc;
};
static void
@ -39,7 +36,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "clock";
}
@ -49,7 +46,7 @@ content(struct module *mod)
{
const struct private *m = mod->private;
time_t t = time(NULL);
struct tm *tm = m->utc ? gmtime(&t) : localtime(&t);
struct tm *tm = localtime(&t);
char date_str[1024];
strftime(date_str, sizeof(date_str), m->date_format, tm);
@ -58,7 +55,8 @@ 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,
};
@ -68,6 +66,7 @@ content(struct module *mod)
return exposable;
}
#include <pthread.h>
static int
run(struct module *mod)
{
@ -75,8 +74,6 @@ 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);
@ -90,12 +87,15 @@ 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}};
if (poll(fds, 1, timeout_ms) < 0) {
if (errno == EINTR)
continue;
poll(fds, 1, timeout_ms);
LOG_ERRNO("failed to poll");
if (fds[0].revents & POLLIN)
break;
}
if (fds[0].revents & POLLIN) {
ret = 0;
break;
}
bar->refresh(bar);
}
return ret;
return 0;
}
static struct module *
clock_new(struct particle *label, const char *date_format, const char *time_format, bool utc)
clock_new(struct particle *label, const char *date_format, const char *time_format)
{
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,7 +160,8 @@ 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;
@ -177,11 +178,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",
utc != NULL ? yml_value_as_bool(utc) : false);
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");
}
static bool
@ -190,7 +191,6 @@ 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,
};

View file

@ -1,297 +0,0 @@
#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

View file

@ -1,13 +0,0 @@
#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

View file

@ -1,350 +0,0 @@
#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, &sectors_read,
&reading_time, &completed_writes, &merged_writes, &sectors_written, &writting_time,
&ios_in_progress, &io_time, &io_weighted_time, &completed_discards, &merged_discards,
&sectors_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

View file

@ -1,550 +0,0 @@
#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

View file

@ -1,666 +0,0 @@
#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, &registry_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

View file

@ -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,11 +41,14 @@ 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) {
@ -99,7 +102,11 @@ 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;
@ -113,7 +120,8 @@ 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
@ -125,7 +133,10 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
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) {
@ -148,11 +159,13 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
/* 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;
}
@ -175,8 +188,10 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
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;
@ -195,10 +210,10 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
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");
@ -247,13 +262,13 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
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;
@ -280,7 +295,7 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
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;
@ -294,7 +309,7 @@ i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void
}
if (pkt_handler != NULL)
err = !pkt_handler(sock, hdr->type, json, data);
err = !pkt_handler(hdr->type, json, data);
else
LOG_DBG("no handler for reply/event %d; ignoring", hdr->type);

View file

@ -2,8 +2,8 @@
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.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 sock, int type, const struct json_object *json, void *data);
typedef bool (*i3_ipc_callback_t)(int type, const struct json_object *json, void *data);
struct i3_ipc_callbacks {
void (*burst_done)(void *data);
@ -43,4 +43,6 @@ 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);

View file

@ -1,27 +1,27 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <assert.h>
#include <threads.h>
#include <fcntl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <tllist.h>
#define LOG_MODULE "i3"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
#include "i3-common.h"
#include "i3-ipc.h"
#include "i3-common.h"
enum sort_mode { SORT_NONE, SORT_NATIVE, SORT_ASCENDING, SORT_DESCENDING };
enum sort_mode {SORT_NONE, SORT_ASCENDING, SORT_DESCENDING};
struct ws_content {
char *name;
@ -29,16 +29,13 @@ struct ws_content {
};
struct workspace {
int id;
char *name;
int name_as_int; /* -1 if name is not a decimal number */
bool persistent;
int name_as_int; /* -1 is name is not a decimal number */
char *output;
bool visible;
bool focused;
bool urgent;
bool empty;
struct {
unsigned id;
@ -48,8 +45,7 @@ struct workspace {
} window;
};
struct private
{
struct private {
int left_spacing;
int right_spacing;
@ -62,61 +58,22 @@ 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 *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");
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");
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);
@ -125,59 +82,48 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
const char *name_as_string = json_object_get_string(name);
const size_t node_count = focus != NULL ? json_object_array_length(focus) : 0;
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 bool is_empty = node_count == 0;
int name_as_int = workspace_name_as_int(name_as_string);
name_as_int *= 10;
name_as_int += *p - '0';
}
*ws = (struct workspace){
.id = json_object_get_int(id),
*ws = (struct workspace) {
.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},
};
return true;
}
static void
workspace_free_persistent(struct workspace *ws)
{
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
workspace_free(struct workspace *ws)
{
workspace_free_persistent(ws);
free(ws->name);
ws->name = NULL;
free(ws->output);
free(ws->window.title);
free(ws->window.application);
}
static void
workspaces_free(struct private *m, bool free_persistent)
workspaces_free(struct private *m)
{
tll_foreach(m->workspaces, it)
{
if (free_persistent || !it->item.persistent) {
workspace_free(&it->item);
tll_remove(m->workspaces, it);
}
}
workspace_free(&it->item);
tll_free(m->workspaces);
}
static void
workspace_add(struct private *m, struct workspace ws)
{
@ -186,26 +132,9 @@ 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) {
@ -214,9 +143,10 @@ 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;
}
@ -227,16 +157,14 @@ 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) {
@ -251,13 +179,12 @@ workspace_add(struct private *m, struct workspace ws)
}
static void
workspace_del(struct private *m, int id)
workspace_del(struct private *m, const char *name)
{
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
struct workspace *ws = &it->item;
if (ws->id != id)
if (strcmp(ws->name, name) != 0)
continue;
workspace_free(ws);
@ -267,22 +194,9 @@ workspace_del(struct private *m, int id)
}
static struct workspace *
workspace_lookup(struct private *m, int id)
workspace_lookup(struct private *m, const char *name)
{
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)
{
tll_foreach(m->workspaces, it) {
struct workspace *ws = &it->item;
if (strcmp(ws->name, name) == 0)
return ws;
@ -291,7 +205,7 @@ workspace_lookup_by_name(struct private *m, const char *name)
}
static bool
handle_get_version_reply(int sock, int type, const struct json_object *json, void *_m)
handle_get_version_reply(int type, const struct json_object *json, void *_m)
{
struct json_object *version;
if (!json_object_object_get_ex(json, "human_readable", &version)) {
@ -304,7 +218,7 @@ handle_get_version_reply(int sock, int type, const struct json_object *json, voi
}
static bool
handle_subscribe_reply(int sock, int type, const struct json_object *json, void *_m)
handle_subscribe_reply(int type, const struct json_object *json, void *_m)
{
struct json_object *success;
if (!json_object_object_get_ex(json, "success", &success)) {
@ -321,88 +235,36 @@ handle_subscribe_reply(int sock, int type, const struct json_object *json, void
}
static bool
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 its an empty, persistent
* workspace, and if so, use it.
*
* This is necessary, since empty, persistent workspaces dont
* exist in the i3/Sway server, and thus we dont _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)
handle_get_workspaces_reply(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, false);
workspaces_free(m);
m->dirty = true;
size_t count = json_object_array_length(json);
for (size_t i = 0; i < count; i++) {
if (!workspace_update_or_add(m, json_object_array_get_idx(json, i)))
goto err;
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);
}
mtx_unlock(&mod->lock);
return true;
err:
workspaces_free(m, false);
mtx_unlock(&mod->lock);
return false;
}
static bool
handle_workspace_event(int sock, int type, const struct json_object *json, void *_mod)
handle_workspace_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -418,59 +280,67 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
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;
struct json_object *current, *_current_id;
if ((!json_object_object_get_ex(json, "current", &current)
|| !json_object_object_get_ex(current, "id", &_current_id))
&& !is_reload) {
LOG_ERR("workspace event without 'current' and/or 'id' properties");
if (is_reload) {
LOG_WARN("unimplemented: 'reload' event");
return true;
}
struct json_object *current, *_current_name;
if (!json_object_object_get_ex(json, "current", &current) ||
!json_object_object_get_ex(current, "name", &_current_name))
{
LOG_ERR("workspace event without 'current' and/or 'name' properties");
return false;
}
int current_id = json_object_get_int(_current_id);
const char *current_name = json_object_get_string(_current_name);
mtx_lock(&mod->lock);
if (is_init) {
if (!workspace_update_or_add(m, current))
goto err;
}
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;
else if (is_empty) {
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;
workspace_add(m, ws);
}
}
else if (is_empty) {
assert(workspace_lookup(m, current_name) != NULL);
workspace_del(m, current_name);
}
else if (is_focused) {
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)) {
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))
{
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_id);
struct workspace *w = workspace_lookup(m, current_name);
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 (ws->output != NULL && strcmp(ws->output, w->output) == 0)
if (strcmp(ws->output, w->output) == 0)
ws->visible = false;
}
@ -479,67 +349,12 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
w->visible = true;
/* Old workspace is no longer focused */
int old_id = json_object_get_int(_old_id);
struct workspace *old_w = workspace_lookup(m, old_id);
const char *old_name = json_object_get_string(_old_name);
struct workspace *old_w = workspace_lookup(m, old_name);
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)) {
@ -548,20 +363,10 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
return false;
}
struct workspace *w = workspace_lookup(m, current_id);
struct workspace *w = workspace_lookup(m, current_name);
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;
@ -572,7 +377,7 @@ err:
}
static bool
handle_window_event(int sock, int type, const struct json_object *json, void *_mod)
handle_window_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -594,9 +399,8 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
mtx_lock(&mod->lock);
struct workspace *ws = NULL;
__attribute__((unused)) size_t focused = 0;
tll_foreach(m->workspaces, it)
{
size_t focused = 0;
tll_foreach(m->workspaces, it) {
if (it->item.focused) {
ws = &it->item;
focused++;
@ -606,20 +410,6 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
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);
@ -631,6 +421,23 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
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);
@ -651,24 +458,27 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
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 | O_CLOEXEC);
int fd = open(path, O_RDONLY);
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;
@ -693,7 +503,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
}
static bool
handle_mode_event(int sock, int type, const struct json_object *json, void *_mod)
handle_mode_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -735,7 +545,7 @@ run(struct module *mod)
if (!i3_get_socket_address(&addr))
return 1;
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
LOG_ERRNO("failed to create UNIX socket");
return 1;
@ -748,27 +558,6 @@ 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);
@ -800,11 +589,7 @@ destroy(struct module *mod)
}
free(m->ws_content.v);
workspaces_free(m, true);
for (size_t i = 0; i < m->persistent_count; i++)
free(m->persistent_workspaces[i]);
free(m->persistent_workspaces);
workspaces_free(m);
free(m->mode);
free(m);
@ -824,7 +609,7 @@ ws_content_for_name(struct private *m, const char *name)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "i3/sway";
}
@ -840,47 +625,30 @@ 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";
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;
}
const char *state =
ws->urgent ? "urgent" :
ws->visible ? ws->focused ? "focused" : "unfocused" :
"invisible";
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "name", name),
tag_new_string(mod, "output", ws->output),
tag_new_string(mod, "name", ws->name),
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),
@ -888,7 +656,7 @@ content(struct module *mod)
tag_new_string(mod, "mode", m->mode),
},
.count = 10,
.count = 8,
};
if (ws->focused) {
@ -898,9 +666,12 @@ 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);
@ -910,7 +681,8 @@ 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. */
@ -920,9 +692,8 @@ 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, size_t persistent_count, const char *persistent_workspaces[static persistent_count],
bool strip_workspace_numbers)
i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
int left_spacing, int right_spacing, enum sort_mode sort_mode)
{
struct private *m = calloc(1, sizeof(*m));
@ -938,15 +709,8 @@ i3_new(struct i3_workspaces workspaces[], size_t workspace_count, int left_spaci
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;
@ -964,55 +728,50 @@ 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, "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);
}
}
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;
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, persistent_count, persistent_workspaces,
(strip_workspace_number != NULL ? yml_value_as_bool(strip_workspace_number) : false));
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode);
}
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;
}
@ -1028,25 +787,18 @@ 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", "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);
return conf_verify_enum(
chain, node, (const char *[]){"none", "ascending", "descending"}, 3);
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"spacing", false, &conf_verify_unsigned},
{"left-spacing", false, &conf_verify_unsigned},
{"right-spacing", false, &conf_verify_unsigned},
{"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_int},
{"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},
@ -1061,5 +813,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

View file

@ -1,14 +1,16 @@
#include <assert.h>
#include <stdlib.h>
#include <assert.h>
#include <poll.h>
#include "../config-verify.h"
#include "../config.h"
#include "../config-verify.h"
#include "../module.h"
#include "../plugin.h"
struct private { struct particle *label; };
struct private {
struct particle *label;
};
static void
destroy(struct module *mod)
@ -20,7 +22,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "label";
}

View file

@ -1,200 +0,0 @@
#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

View file

@ -2,157 +2,35 @@ module_sdk = declare_dependency(dependencies: [pixman, threads, tllist, fcft])
modules = []
# 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()
alsa = dependency('alsa')
udev = dependency('libudev')
json = dependency('json-c')
mpd = dependency('libmpdclient')
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
# Module name -> (source-list, dep-list)
mod_data = {}
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]],
}
if plugin_alsa_enabled
mod_data += {'alsa': [[], [m, alsa]]}
if backend_x11
mod_data += {
'xkb': [[], [xcb_stuff, xcb_xkb]],
'xwindow': [[], [xcb_stuff]],
}
endif
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
if backend_wayland
river_proto_headers = []
river_proto_src = []
@ -171,29 +49,9 @@ if plugin_river_enabled
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach
mod_data += {'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist, wayland_client]]}
endif
if plugin_foreign_toplevel_enabled
ftop_proto_headers = []
ftop_proto_src = []
foreach prot : ['../external/wlr-foreign-toplevel-management-unstable-v1.xml']
ftop_proto_headers += custom_target(
prot.underscorify() + '-client-header',
output: '@BASENAME@.h',
input: prot,
command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@'])
ftop_proto_src += custom_target(
prot.underscorify() + '-private-code',
output: '@BASENAME@.c',
input: prot,
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach
mod_data += {'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [m, dynlist, wayland_client]]}
mod_data += {
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]],
}
endif
foreach mod, data : mod_data

View file

@ -1,34 +1,33 @@
#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 <libgen.h>
#include <poll.h>
#include <libgen.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 "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h"
struct private
{
struct private {
char *host;
uint16_t port;
struct particle *label;
@ -39,12 +38,10 @@ struct private
bool repeat;
bool random;
bool consume;
bool single;
int volume;
int volume;
char *album;
char *artist;
char *title;
char *file;
struct {
uint64_t value;
@ -62,9 +59,11 @@ 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);
}
@ -76,7 +75,6 @@ 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);
@ -86,7 +84,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "mpd";
}
@ -132,11 +130,12 @@ 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;
@ -153,23 +152,16 @@ 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 *[]){
@ -177,19 +169,17 @@ 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 = 14,
.count = 12,
};
mtx_unlock(&mod->lock);
@ -233,7 +223,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 | SOCK_CLOEXEC, 0);
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1);
@ -244,7 +234,8 @@ 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);
@ -255,15 +246,12 @@ 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}
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
poll(fds, 2, -1);
if (fds[0].revents & POLLIN) {
ret = true;
@ -275,7 +263,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);
@ -285,7 +273,7 @@ wait_for_socket_create(const struct module *mod)
break;
}
ptr += sizeof(*e) + e->len;
ptr += sizeof(*e) + e->len;
}
}
@ -308,7 +296,8 @@ 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;
}
@ -326,7 +315,8 @@ 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;
}
@ -338,7 +328,6 @@ 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);
@ -349,37 +338,30 @@ 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->file);
m->file = NULL;
free(m->album); m->album = NULL;
free(m->artist); m->artist = NULL;
free(m->title); m->title = 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);
@ -395,9 +377,8 @@ run(struct module *mod)
struct private *m = mod->private;
bool aborted = false;
int ret = 0;
while (!aborted && ret == 0) {
while (!aborted) {
if (m->conn != NULL) {
mpd_connection_free(m->conn);
@ -406,21 +387,16 @@ 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->file);
m->file = NULL;
free(m->album); m->album = NULL;
free(m->artist); m->artist = NULL;
free(m->title); m->title = 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 && ret == 0) {
while (!aborted) {
if (m->port == 0) {
/* Use inotify to watch for socket creation */
aborted = wait_for_socket_create(mod);
@ -438,33 +414,16 @@ run(struct module *mod)
* host), wait for a while until we try to re-connect
* again.
*/
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;
}
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int res = poll(fds, 1, 10 * 1000);
if (res == 1) {
assert(fds[0].revents & POLLIN);
aborted = true;
}
}
if (aborted || ret != 0)
if (aborted)
break;
/* Initial state (after establishing a connection) */
@ -482,18 +441,12 @@ 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;
}
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
ret = 1;
break;
}
poll(fds, 2, -1);
if (fds[0].revents & POLLIN) {
aborted = true;
@ -506,7 +459,8 @@ 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);
@ -523,7 +477,7 @@ run(struct module *mod)
m->conn = NULL;
}
return aborted ? 0 : ret;
return 0;
}
struct refresh_context {
@ -578,7 +532,9 @@ 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;
}
@ -618,7 +574,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;
}
@ -649,8 +605,10 @@ 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
@ -658,7 +616,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_unsigned},
{"port", false, &conf_verify_int},
MODULE_COMMON_ATTRS,
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,377 +0,0 @@
#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;
}

View file

@ -1,45 +0,0 @@
#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);

View file

@ -1,160 +0,0 @@
#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

View file

@ -1,163 +0,0 @@
#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

File diff suppressed because it is too large Load diff

View file

@ -1,523 +0,0 @@
#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