Compare commits

..

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

128 changed files with 3454 additions and 12278 deletions

View file

@ -19,17 +19,13 @@ packages:
- json-c-dev - json-c-dev
- libmpdclient-dev - libmpdclient-dev
- alsa-lib-dev - alsa-lib-dev
- pulseaudio-dev
- pipewire-dev
- ttf-dejavu - ttf-dejavu
- gcovr - gcovr
- python3 - python3
- py3-pip - py3-pip
- flex
- bison
sources: sources:
- https://git.sr.ht/~dnkl/yambar - https://codeberg.org/dnkl/yambar
# triggers: # triggers:
# - action: email # - action: email
@ -37,10 +33,10 @@ sources:
# to: <comitter> # to: <comitter>
tasks: tasks:
- fcft: | - codespell: |
cd yambar/subprojects pip install codespell
git clone https://codeberg.org/dnkl/fcft.git cd yambar
cd ../.. ~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd
- setup: | - setup: |
mkdir -p bld/debug bld/release bld/x11-only bld/wayland-only bld/plugs-are-shared 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 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,10 +1,5 @@
# Changelog # 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.7.0](#1-7-0)
* [1.6.2](#1-6-2) * [1.6.2](#1-6-2)
* [1.6.1](#1-6-1) * [1.6.1](#1-6-1)
@ -12,445 +7,22 @@
* [1.5.0](#1-5-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 ## 1.7.0
### Added ### Added
* i3: `persistent` attribute, allowing persistent workspaces * i3: `persistent` attribute, allowing persistent workspaces
([#72](https://codeberg.org/dnkl/yambar/issues/72)). (https://codeberg.org/dnkl/yambar/issues/72).
* bar: `border.{left,right,top,bottom}-width`, allowing the width of * bar: `border.{left,right,top,bottom}-width`, allowing the width of
each side of the border to be configured each side of the border to be configured
individually. `border.width` is now a short-hand for setting all individually. `border.width` is now a short-hand for setting all
four borders to the same value four borders to the same value
([#77](https://codeberg.org/dnkl/yambar/issues/77)). (https://codeberg.org/dnkl/yambar/issues/77).
* bar: `layer: top|bottom`, allowing the layer which the bar is * bar: `layer: top|bottom`, allowing the layer which the bar is
rendered on to be changed. Wayland only - ignored on X11. rendered on to be changed. Wayland only - ignored on X11.
* river: `all-monitors: false|true`. * river: `all-monitors: false|true`.
* `-d,--log-level=info|warning|error|none` command line option * `-d,--log-level=info|warning|error|none` command line option
([#84](https://codeberg.org/dnkl/yambar/issues/84)). (https://codeberg.org/dnkl/yambar/issues/84).
* river: support for the river-status protocol, version 2 (urgent * river: support for the river-status protocol, version 2 (urgent
views). views).
* `online` tag to the `alsa` module. * `online` tag to the `alsa` module.
@ -462,16 +34,17 @@
* network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags. * network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags.
* network: `poll-interval` option (for the new `signal` and * network: `poll-interval` option (for the new `signal` and
`*-bitrate` tags). `*-bitrate` tags).
* tags: percentage formatter, for range tags: `{tag_name:%}`.
* tags: percentage tag formatter, for range tags: `{tag_name:%}`. * tags: percentage tag formatter, for range tags: `{tag_name:%}`.
* tags: kb/mb/gb, and kib/mib/gib tag formatters. * tags: kb/mb/gb, and kib/mib/gib tag formatters.
* clock: add a config option to show UTC time.
### Changed ### Changed
* bar: do not add `spacing` around empty (zero-width) modules. * 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 * 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 if we get disconnected. Instead, keep retrying until we succeed
([#86](https://codeberg.org/dnkl/yambar/issues/86)). (https://codeberg.org/dnkl/yambar/issues/86).
### Fixed ### Fixed
@ -481,7 +54,7 @@
* Regression: `{where}` tag not being expanded in progress-bar * Regression: `{where}` tag not being expanded in progress-bar
`on-click` handlers. `on-click` handlers.
* `alsa` module causing yambar to use 100% CPU if the ALSA device is * `alsa` module causing yambar to use 100% CPU if the ALSA device is
disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)). disconnected (https://codeberg.org/dnkl/yambar/issues/61).
### Contributors ### Contributors
@ -497,39 +70,39 @@
* Text shaping support. * Text shaping support.
* Support for middle and right mouse buttons, mouse wheel and trackpad * 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 * 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 ### Changed
* doc: split up **yambar-modules**(5) into multiple man pages, one for * 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. * fcft >= 2.4.0 is now required.
* sway-xkb: non-keyboard inputs are now ignored * 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 * battery: dont terminate (causing last status to “freeze”) when
failing to update; retry again later 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 * battery: differentiate "Not Charging" and "Discharging" in state
tag of battery module. 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 * string: use HORIZONTAL ELLIPSIS instead of three regular periods
when truncating a string when truncating a string
([#73](https://codeberg.org/dnkl/yambar/issues/73)). (https://codeberg.org/dnkl/yambar/issues/73).
### Fixed ### Fixed
* Crash when merging non-dictionary anchors in the YAML configuration * 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 * 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 `{}` * 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 * `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 ### Contributors
@ -544,7 +117,7 @@
* i3: workspaces with numerical names are sorted separately from * i3: workspaces with numerical names are sorted separately from
non-numerically named workspaces non-numerically named workspaces
([#30](https://codeberg.org/dnkl/yambar/issues/30)). (https://codeberg.org/dnkl/yambar/issues/30).
### Fixed ### Fixed
@ -552,7 +125,7 @@
* mpd: `elapsed` tag not working (regression, introduced in 1.6.0). * mpd: `elapsed` tag not working (regression, introduced in 1.6.0).
* Wrong background color for (semi-) transparent backgrounds. * Wrong background color for (semi-) transparent backgrounds.
* battery: stats sometimes getting stuck at 0, or impossibly large * 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 ## 1.6.0
@ -561,17 +134,17 @@
* alsa: `percent` tag. This is an integer tag that represents the * alsa: `percent` tag. This is an integer tag that represents the
current volume as a percentage value current volume as a percentage value
([#10](https://codeberg.org/dnkl/yambar/issues/10)). (https://codeberg.org/dnkl/yambar/issues/10).
* river: added documentation * 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 * 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 * mpd: `volume` tag. This is a range tag that represents MPD's current
volume in percentage (0-100) volume in percentage (0-100)
* i3: `sort` configuration option, that controls how the workspace * i3: `sort` configuration option, that controls how the workspace
list is sorted. Can be set to one of `none`, `ascending` or list is sorted. Can be set to one of `none`, `ascending` or
`descending`. Default is `none` `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 * i3: `mode` tag: the name of the currently active mode
@ -581,12 +154,12 @@
error”_. error”_.
* Memory leak when a YAML parsing error was encountered. * Memory leak when a YAML parsing error was encountered.
* clock: update every second when necessary * 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 * 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. * Crash when the alpha component in a color value was 0.
* XCB: Fallback to non-primary monitor when the primary monitor is * 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 ### Contributors

View file

@ -1,8 +1,7 @@
pkgname=yambar pkgname=yambar
pkgver=1.11.0 pkgver=1.7.0
pkgrel=1 pkgrel=1
pkgdesc="Simplistic and highly configurable status panel for X and Wayland" pkgdesc="Simplistic and highly configurable status panel for X and Wayland"
changelog=CHANGELOG.md
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
url=https://codeberg.org/dnkl/yambar url=https://codeberg.org/dnkl/yambar
license=(mit) license=(mit)
@ -16,9 +15,7 @@ depends=(
'libudev.so' 'libudev.so'
'json-c' 'json-c'
'libmpdclient' 'libmpdclient'
'libpulse' 'fcft>=2.4.0')
'pipewire'
'fcft>=3.0.0' 'fcft<4.0.0')
optdepends=('xcb-util-errors: better X error messages') optdepends=('xcb-util-errors: better X error messages')
source=() source=()

View file

@ -1,5 +1,5 @@
pkgname=yambar-wayland pkgname=yambar-wayland
pkgver=1.11.0 pkgver=1.7.0
pkgrel=1 pkgrel=1
pkgdesc="Simplistic and highly configurable status panel for Wayland" pkgdesc="Simplistic and highly configurable status panel for Wayland"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
@ -16,11 +16,8 @@ depends=(
'libudev.so' 'libudev.so'
'json-c' 'json-c'
'libmpdclient' 'libmpdclient'
'libpulse' 'fcft>=2.4.0')
'pipewire'
'fcft>=3.0.0' 'fcft<4.0.0')
source=() source=()
changelog=CHANGELOG.md
pkgver() { pkgver() {
cd ../.git &> /dev/null && git describe --tags --long | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || 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 # 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 ## Index
@ -59,9 +57,9 @@ bar:
right: right:
- clock: - clock:
content: 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: "{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}"} - string: {text: "{time}"}
``` ```
@ -78,17 +76,10 @@ Available modules:
* backlight * backlight
* battery * battery
* clock * clock
* cpu
* disk-io
* dwl
* foreign-toplevel
* i3 (and Sway) * i3 (and Sway)
* label * label
* mem
* mpd * mpd
* network * network
* pipewire
* pulse
* removables * removables
* river * river
* script (see script [examples](examples/scripts)) * 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 Second, configure the build (if you intend to install it globally, you
might also want `--prefix=/usr`): might also want `--prefix=/usr`):
```sh ```sh
meson setup --buildtype=release ../.. meson --buildtype=release ../..
``` ```
Optionally, explicitly disable a backend (or enable, if you want a Optionally, explicitly disable a backend (or enable, if you want a

View file

@ -7,8 +7,10 @@
struct backend { struct backend {
bool (*setup)(struct bar *bar); bool (*setup)(struct bar *bar);
void (*cleanup)(struct bar *bar); void (*cleanup)(struct bar *bar);
void (*loop)(struct bar *bar, void (*expose)(const struct bar *bar), void (*loop)(struct bar *bar,
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y)); 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 (*commit)(const struct bar *bar);
void (*refresh)(const struct bar *bar); void (*refresh)(const struct bar *bar);
void (*set_cursor)(struct bar *bar, const char *cursor); void (*set_cursor)(struct bar *bar, const char *cursor);

View file

@ -1,15 +1,15 @@
#include "bar.h" #include "bar.h"
#include "private.h" #include "private.h"
#include <assert.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <threads.h> #include <threads.h>
#include <assert.h>
#include <unistd.h> #include <unistd.h>
#include <pthread.h>
#include <sys/eventfd.h> #include <sys/eventfd.h>
@ -18,17 +18,17 @@
#include "../log.h" #include "../log.h"
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
#include "xcb.h" #include "xcb.h"
#endif #endif
#if defined(ENABLE_WAYLAND) #if defined(ENABLE_WAYLAND)
#include "wayland.h" #include "wayland.h"
#endif #endif
#define max(x, y) ((x) > (y) ? (x) : (y)) #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 * Note: begin_expose() must have been called
*/ */
static void static void
@ -75,8 +75,9 @@ expose(const struct bar *_bar)
const struct private *bar = _bar->private; const struct private *bar = _bar->private;
pixman_image_t *pix = bar->pix; pixman_image_t *pix = bar->pix;
pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &bar->background, 1, pixman_image_fill_rectangles(
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border}); PIXMAN_OP_SRC, pix, &bar->background, 1,
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border});
pixman_image_fill_rectangles( pixman_image_fill_rectangles(
PIXMAN_OP_OVER, pix, &bar->border.color, 4, PIXMAN_OP_OVER, pix, &bar->border.color, 4,
@ -85,15 +86,20 @@ expose(const struct bar *_bar)
{0, 0, bar->border.left_width, bar->height_with_border}, {0, 0, bar->border.left_width, bar->height_with_border},
/* Right */ /* Right */
{bar->width - bar->border.right_width, 0, bar->border.right_width, bar->height_with_border}, {bar->width - bar->border.right_width,
0, bar->border.right_width, bar->height_with_border},
/* Top */ /* Top */
{bar->border.left_width, 0, bar->width - bar->border.left_width - bar->border.right_width, {bar->border.left_width,
0,
bar->width - bar->border.left_width - bar->border.right_width,
bar->border.top_width}, bar->border.top_width},
/* Bottom */ /* Bottom */
{bar->border.left_width, bar->height_with_border - bar->border.bottom_width, {bar->border.left_width,
bar->width - bar->border.left_width - bar->border.right_width, bar->border.bottom_width}, bar->height_with_border - bar->border.bottom_width,
bar->width - bar->border.left_width - bar->border.right_width,
bar->border.bottom_width},
}); });
for (size_t i = 0; i < bar->left.count; i++) { for (size_t i = 0; i < bar->left.count; i++) {
@ -128,14 +134,6 @@ expose(const struct bar *_bar)
int y = bar->border.top_width; int y = bar->border.top_width;
int x = bar->border.left_width + bar->left_margin - bar->left_spacing; int x = bar->border.left_width + bar->left_margin - bar->left_spacing;
pixman_region32_t clip;
pixman_region32_init_rect(
&clip, bar->border.left_width + bar->left_margin, bar->border.top_width,
(bar->width - bar->left_margin - bar->right_margin - bar->border.left_width - bar->border.right_width),
bar->height);
pixman_image_set_clip_region32(pix, &clip);
pixman_region32_fini(&clip);
for (size_t i = 0; i < bar->left.count; i++) { for (size_t i = 0; i < bar->left.count; i++) {
const struct exposable *e = bar->left.exps[i]; const struct exposable *e = bar->left.exps[i];
e->expose(e, pix, x + bar->left_spacing, y, bar->height); e->expose(e, pix, x + bar->left_spacing, y, bar->height);
@ -151,7 +149,11 @@ expose(const struct bar *_bar)
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.right_width);
for (size_t i = 0; i < bar->right.count; i++) { for (size_t i = 0; i < bar->right.count; i++) {
const struct exposable *e = bar->right.exps[i]; const struct exposable *e = bar->right.exps[i];
@ -163,6 +165,7 @@ expose(const struct bar *_bar)
bar->backend.iface->commit(_bar); bar->backend.iface->commit(_bar);
} }
static void static void
refresh(const struct bar *bar) refresh(const struct bar *bar)
{ {
@ -185,12 +188,15 @@ output_name(const struct bar *bar)
} }
static void 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; struct private *bar = _bar->private;
if ((y < bar->border.top_width || y >= (bar->height_with_border - bar->border.bottom_width)) if ((y < bar->border.top_width ||
|| (x < bar->border.left_width || x >= (bar->width - bar->border.right_width))) { y >= (bar->height_with_border - bar->border.bottom_width)) ||
(x < bar->border.left_width || x >= (bar->width - bar->border.right_width)))
{
set_cursor(_bar, "left_ptr"); set_cursor(_bar, "left_ptr");
return; return;
} }
@ -232,7 +238,10 @@ on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x,
mx += e->width + bar->right_spacing; 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.right_width);
for (size_t i = 0; i < bar->right.count; i++) { for (size_t i = 0; i < bar->right.count; i++) {
struct exposable *e = bar->right.exps[i]; struct exposable *e = bar->right.exps[i];
@ -273,7 +282,8 @@ run(struct bar *_bar)
{ {
struct private *bar = _bar->private; 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 + bar->border.top_width + bar->border.bottom_width;
if (!bar->backend.iface->setup(_bar)) { if (!bar->backend.iface->setup(_bar)) {
bar->backend.iface->cleanup(_bar); bar->backend.iface->cleanup(_bar);
@ -283,7 +293,6 @@ run(struct bar *_bar)
} }
set_cursor(_bar, "left_ptr"); set_cursor(_bar, "left_ptr");
expose(_bar);
/* Start modules */ /* Start modules */
thrd_t thrd_left[max(bar->left.count, 1)]; thrd_t thrd_left[max(bar->left.count, 1)];
@ -323,26 +332,20 @@ run(struct bar *_bar)
int mod_ret; int mod_ret;
for (size_t i = 0; i < bar->left.count; i++) { for (size_t i = 0; i < bar->left.count; i++) {
thrd_join(thrd_left[i], &mod_ret); thrd_join(thrd_left[i], &mod_ret);
if (mod_ret != 0) { if (mod_ret != 0)
const struct module *m = bar->left.mods[i]; LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, mod_ret);
LOG_ERR("module: LEFT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
}
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
} }
for (size_t i = 0; i < bar->center.count; i++) { for (size_t i = 0; i < bar->center.count; i++) {
thrd_join(thrd_center[i], &mod_ret); thrd_join(thrd_center[i], &mod_ret);
if (mod_ret != 0) { if (mod_ret != 0)
const struct module *m = bar->center.mods[i]; LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, mod_ret);
LOG_ERR("module: CENTER #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
}
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
} }
for (size_t i = 0; i < bar->right.count; i++) { for (size_t i = 0; i < bar->right.count; i++) {
thrd_join(thrd_right[i], &mod_ret); thrd_join(thrd_right[i], &mod_ret);
if (mod_ret != 0) { if (mod_ret != 0)
const struct module *m = bar->right.mods[i]; LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret);
LOG_ERR("module: RIGHT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret);
}
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
} }

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "../color.h" #include "../color.h"
#include "../font-shaping.h"
#include "../module.h" #include "../module.h"
struct bar { struct bar {
@ -18,7 +17,7 @@ struct bar {
}; };
enum bar_location { BAR_TOP, BAR_BOTTOM }; enum bar_location { BAR_TOP, BAR_BOTTOM };
enum bar_layer { BAR_LAYER_OVERLAY, BAR_LAYER_TOP, BAR_LAYER_BOTTOM, BAR_LAYER_BACKGROUND }; enum bar_layer { BAR_LAYER_TOP, BAR_LAYER_BOTTOM };
enum bar_backend { BAR_BACKEND_AUTO, BAR_BACKEND_XCB, BAR_BACKEND_WAYLAND }; enum bar_backend { BAR_BACKEND_AUTO, BAR_BACKEND_XCB, BAR_BACKEND_WAYLAND };
struct bar_config { struct bar_config {
@ -27,7 +26,6 @@ struct bar_config {
const char *monitor; const char *monitor;
enum bar_layer layer; enum bar_layer layer;
enum bar_location location; enum bar_location location;
enum font_shaping font_shaping;
int height; int height;
int left_spacing, right_spacing; int left_spacing, right_spacing;
int left_margin, right_margin; int left_margin, right_margin;

View file

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

View file

@ -3,8 +3,7 @@
#include "../bar/bar.h" #include "../bar/bar.h"
#include "backend.h" #include "backend.h"
struct private struct private {
{
/* From bar_config */ /* From bar_config */
char *monitor; char *monitor;
enum bar_layer layer; enum bar_layer layer;

File diff suppressed because it is too large Load diff

214
bar/xcb.c
View file

@ -1,16 +1,16 @@
#include "xcb.h" #include "xcb.h"
#include <assert.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <unistd.h>
#include <poll.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <unistd.h>
#include <pixman.h> #include <pixman.h>
#include <xcb/xcb.h>
#include <xcb/randr.h> #include <xcb/randr.h>
#include <xcb/render.h> #include <xcb/render.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h> #include <xcb/xcb_aux.h>
#include <xcb/xcb_cursor.h> #include <xcb/xcb_cursor.h>
#include <xcb/xcb_event.h> #include <xcb/xcb_event.h>
@ -39,6 +39,7 @@ struct xcb_backend {
void *client_pixmap; void *client_pixmap;
size_t client_pixmap_size; size_t client_pixmap_size;
pixman_image_t *pix; pixman_image_t *pix;
}; };
void * void *
@ -54,8 +55,11 @@ setup(struct bar *_bar)
struct private *bar = _bar->private; struct private *bar = _bar->private;
struct xcb_backend *backend = bar->backend.data; struct xcb_backend *backend = bar->backend.data;
if (bar->border.left_margin != 0 || bar->border.right_margin != 0 || bar->border.top_margin != 0 if (bar->border.left_margin != 0 ||
|| bar->border.bottom_margin) { bar->border.right_margin != 0 ||
bar->border.top_margin != 0 ||
bar->border.bottom_margin)
{
LOG_WARN("non-zero border margins ignored in X11 backend"); 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_screen_t *screen = xcb_aux_get_screen(backend->conn, default_screen);
xcb_randr_get_monitors_reply_t *monitors xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply(
= xcb_randr_get_monitors_reply(backend->conn, xcb_randr_get_monitors(backend->conn, screen->root, 0), &e); backend->conn,
xcb_randr_get_monitors(backend->conn, screen->root, 0),
&e);
if (e != NULL) { if (e != NULL) {
LOG_ERR("failed to get monitor list: %s", xcb_error(e)); 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 */ /* Find monitor coordinates and width/height */
bool found_monitor = false; bool found_monitor = false;
for (xcb_randr_monitor_info_iterator_t it = xcb_randr_get_monitors_monitors_iterator(monitors); it.rem > 0; for (xcb_randr_monitor_info_iterator_t it =
xcb_randr_monitor_info_next(&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; const xcb_randr_monitor_info_t *mon = it.data;
char *name = get_atom_name(backend->conn, mon->name); 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, LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name,
mon->width_in_millimeters, mon->height_in_millimeters); 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 */ /* User wants a specific monitor, and this is not the one */
if (bar->monitor != NULL && strcmp(bar->monitor, name) != 0) { if (bar->monitor != NULL && strcmp(bar->monitor, name) != 0) {
@ -101,11 +111,14 @@ setup(struct bar *_bar)
backend->x = mon->x; backend->x = mon->x;
backend->y = mon->y; backend->y = mon->y;
bar->width = mon->width; 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; 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 */ /* Exact match */
free(name); free(name);
break; break;
@ -142,47 +155,74 @@ setup(struct bar *_bar)
LOG_DBG("using a %hhu-bit visual", depth); LOG_DBG("using a %hhu-bit visual", depth);
backend->colormap = xcb_generate_id(backend->conn); 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); backend->win = xcb_generate_id(backend->conn);
xcb_create_window( xcb_create_window(
backend->conn, depth, backend->win, screen->root, backend->x, backend->y, bar->width, bar->height_with_border, backend->conn,
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id, depth, backend->win, screen->root,
(XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP), backend->x, backend->y, bar->width, bar->height_with_border,
(const uint32_t[]){screen->black_pixel, screen->white_pixel, 0,
(XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
| XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_STRUCTURE_NOTIFY), (XCB_CW_BACK_PIXEL |
backend->colormap}); 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"; const char *title = "yambar";
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, xcb_change_property(
strlen(title), title); 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, xcb_change_property(
(const uint32_t[]){getpid()}); backend->conn,
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 1, XCB_PROP_MODE_REPLACE, backend->win,
(const uint32_t[]){_NET_WM_WINDOW_TYPE_DOCK}); _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_STATE, XCB_ATOM_ATOM, 32, 2, xcb_change_property(
(const uint32_t[]){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY}); backend->conn,
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, XCB_PROP_MODE_REPLACE, backend->win,
(const uint32_t[]){0xffffffff}); _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 */ /* Always on top */
xcb_configure_window(backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE, xcb_configure_window(
(const uint32_t[]){XCB_STACK_MODE_ABOVE}); 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_strut, bottom_strut;
uint32_t top_pair[2], bottom_pair[2]; uint32_t top_pair[2], bottom_pair[2];
if (bar->location == BAR_TOP) { 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[0] = backend->x;
top_pair[1] = backend->x + bar->width - 1; top_pair[1] = backend->x + bar->width - 1;
bottom_strut = 0; bottom_strut = 0;
bottom_pair[0] = bottom_pair[1] = 0; bottom_pair[0] = bottom_pair[1] = 0;
} else { } else {
bottom_strut = bar->height_with_border; bottom_strut = screen->height_in_pixels - backend->y;
bottom_pair[0] = backend->x; bottom_pair[0] = backend->x;
bottom_pair[1] = backend->x + bar->width - 1; bottom_pair[1] = backend->x + bar->width - 1;
@ -192,38 +232,42 @@ setup(struct bar *_bar)
uint32_t strut[] = { uint32_t strut[] = {
/* left/right/top/bottom */ /* left/right/top/bottom */
0, 0, 0,
0,
top_strut, top_strut,
bottom_strut, bottom_strut,
/* start/end pairs for left/right/top/bottom */ /* start/end pairs for left/right/top/bottom */
0, 0, 0,
0, 0, 0,
0, top_pair[0], top_pair[1],
0, bottom_pair[0], bottom_pair[1],
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, xcb_change_property(
strut); 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, xcb_change_property(
32, 12, strut); 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); backend->gc = xcb_generate_id(backend->conn);
xcb_create_gc(backend->conn, backend->gc, backend->win, XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES, xcb_create_gc(backend->conn, backend->gc, backend->win,
(const uint32_t[]){screen->white_pixel, 0}); 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_size = stride * bar->height_with_border;
backend->client_pixmap = malloc(backend->client_pixmap_size); backend->client_pixmap = malloc(backend->client_pixmap_size);
backend->pix = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, bar->width, bar->height_with_border, backend->pix = pixman_image_create_bits_no_clear(
(uint32_t *)backend->client_pixmap, stride); PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
(uint32_t *)backend->client_pixmap, stride);
bar->pix = backend->pix; bar->pix = backend->pix;
xcb_map_window(backend->conn, backend->win); xcb_map_window(backend->conn, backend->win);
@ -266,8 +310,10 @@ cleanup(struct bar *_bar)
} }
static void static void
loop(struct bar *_bar, void (*expose)(const struct bar *bar), loop(struct bar *_bar,
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y)) 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 private *bar = _bar->private;
struct xcb_backend *backend = bar->backend.data; 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); const int fd = xcb_get_file_descriptor(backend->conn);
while (true) { 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); 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) { if (fds[1].revents & POLLHUP) {
LOG_WARN("disconnected from XCB"); 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"); LOG_ERRNO("failed to signal abort to modules");
} }
break; break;
} }
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn); e != NULL; for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn);
e = xcb_poll_for_event(backend->conn)) { e != NULL;
e = xcb_poll_for_event(backend->conn))
{
switch (XCB_EVENT_RESPONSE_TYPE(e)) { switch (XCB_EVENT_RESPONSE_TYPE(e)) {
case 0: case 0:
LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e)); 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; const xcb_button_release_event_t *evt = (void *)e;
switch (evt->detail) { switch (evt->detail) {
case 1: case 1: case 2: case 3: case 4: case 5:
case 2: on_mouse(_bar, ON_MOUSE_CLICK,
case 3: evt->detail, evt->event_x, evt->event_y);
case 4:
case 5:
on_mouse(_bar, ON_MOUSE_CLICK, evt->detail, evt->event_x, evt->event_y);
break; break;
} }
break; break;
@ -355,9 +405,10 @@ commit(const struct bar *_bar)
const struct private *bar = _bar->private; const struct private *bar = _bar->private;
const struct xcb_backend *backend = bar->backend.data; const struct xcb_backend *backend = bar->backend.data;
xcb_put_image(backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc, bar->width, xcb_put_image(
bar->height_with_border, 0, 0, 0, backend->depth, backend->client_pixmap_size, backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc,
backend->client_pixmap); bar->width, bar->height_with_border, 0, 0, 0,
backend->depth, backend->client_pixmap_size, backend->client_pixmap);
xcb_flush(backend->conn); xcb_flush(backend->conn);
} }
@ -369,19 +420,23 @@ refresh(const struct bar *_bar)
/* Send an event to handle refresh from main thread */ /* 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 */ * the size of the event structure */
xcb_expose_event_t *evt = calloc(32, 1); xcb_expose_event_t *evt = calloc(32, 1);
*evt = (xcb_expose_event_t){.response_type = XCB_EXPOSE, *evt = (xcb_expose_event_t){
.window = backend->win, .response_type = XCB_EXPOSE,
.x = 0, .window = backend->win,
.y = 0, .x = 0,
.width = bar->width, .y = 0,
.height = bar->height, .width = bar->width,
.count = 1}; .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); xcb_flush(backend->conn);
free(evt); free(evt);
@ -403,7 +458,8 @@ set_cursor(struct bar *_bar, const char *cursor)
xcb_free_cursor(backend->conn, backend->cursor); xcb_free_cursor(backend->conn, backend->cursor);
backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor); backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor);
xcb_change_window_attributes(backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor); xcb_change_window_attributes(
backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
} }
static const char * static const char *

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,6 @@ _arguments \
'(-c --config)'{-c,--config}'[alternative configuration file]:filename:_files' \ '(-c --config)'{-c,--config}'[alternative configuration file]:filename:_files' \
'(-C --validate)'{-C,--validate}'[verify configuration then quit]' \ '(-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' \ '(-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)' \ '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error none)' \
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-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]' '(-s --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging]'

View file

@ -1,7 +1,7 @@
#include "config.h" #include "config.h"
#include <assert.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <tllist.h> #include <tllist.h>
@ -16,9 +16,11 @@ conf_err_prefix(const keychain_t *chain, const struct yml_node *node)
static char msg[4096]; static char msg[4096];
int idx = 0; 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 "." */ /* Remove trailing "." */
msg[idx - 1] = '\0'; msg[idx - 1] = '\0';
@ -43,17 +45,8 @@ conf_verify_int(keychain_t *chain, const struct yml_node *node)
if (yml_value_is_int(node)) if (yml_value_is_int(node))
return true; return true;
LOG_ERR("%s: value is not an integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node)); LOG_ERR("%s: value is not an integer: '%s'",
return false; conf_err_prefix(chain, node), yml_value_as_string(node));
}
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; return false;
} }
@ -63,7 +56,8 @@ conf_verify_bool(keychain_t *chain, const struct yml_node *node)
if (yml_value_is_bool(node)) if (yml_value_is_bool(node))
return true; 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 a boolean: '%s'",
conf_err_prefix(chain, node), yml_value_as_string(node));
return false; return false;
} }
@ -76,7 +70,10 @@ conf_verify_list(keychain_t *chain, const struct yml_node *node,
return false; 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)) if (!verify(chain, iter.node))
return false; return false;
} }
@ -85,7 +82,8 @@ conf_verify_list(keychain_t *chain, const struct yml_node *node,
} }
bool 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); const char *s = yml_value_as_string(node);
if (s == NULL) { if (s == NULL) {
@ -106,7 +104,8 @@ conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *val
} }
bool 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)) { if (!yml_is_dict(node)) {
LOG_ERR("%s: must be a dictionary", conf_err_prefix(chain, node)); LOG_ERR("%s: must be a dictionary", conf_err_prefix(chain, node));
@ -121,7 +120,10 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct at
bool exists[count]; bool exists[count];
memset(exists, 0, sizeof(exists)); 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); const char *key = yml_value_as_string(it.key);
if (key == NULL) { if (key == NULL) {
LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key)); LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key));
@ -161,43 +163,21 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct at
return true; 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 bool
conf_verify_on_click(keychain_t *chain, const struct yml_node *node) conf_verify_on_click(keychain_t *chain, const struct yml_node *node)
{ {
/* on-click: <command> */ /* on-click: <command> */
const char *s = yml_value_as_string(node); const char *s = yml_value_as_string(node);
if (s != NULL) if (s != NULL)
return verify_on_click_path(chain, node); return true;
static const struct attr_info info[] = { static const struct attr_info info[] = {
{"left", false, &verify_on_click_path}, {"middle", false, &verify_on_click_path}, {"left", false, &conf_verify_string},
{"right", false, &verify_on_click_path}, {"wheel-up", false, &verify_on_click_path}, {"middle", false, &conf_verify_string},
{"wheel-down", false, &verify_on_click_path}, {"previous", false, &verify_on_click_path}, {"right", false, &conf_verify_string},
{"next", false, &verify_on_click_path}, {NULL, false, NULL}, {"wheel-up", false, &conf_verify_string},
{"wheel-down", false, &conf_verify_string},
{NULL, false, NULL},
}; };
return conf_verify_dict(chain, node, info); return conf_verify_dict(chain, node, info);
@ -216,30 +196,27 @@ conf_verify_color(keychain_t *chain, const struct yml_node *node)
int v = sscanf(s, "%02x%02x%02x%02x", &r, &g, &b, &a); int v = sscanf(s, "%02x%02x%02x%02x", &r, &g, &b, &a);
if (strlen(s) != 8 || v != 4) { 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 false;
} }
return true; return true;
} }
bool bool
conf_verify_font(keychain_t *chain, const struct yml_node *node) conf_verify_font(keychain_t *chain, const struct yml_node *node)
{ {
if (!yml_is_scalar(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 false;
} }
return true; 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 bool
conf_verify_decoration(keychain_t *chain, const struct yml_node *node) conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
{ {
@ -247,8 +224,7 @@ conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
if (yml_dict_length(node) != 1) { if (yml_dict_length(node) != 1) {
LOG_ERR("%s: decoration must be a dictionary with a single key; " LOG_ERR("%s: decoration must be a dictionary with a single key; "
"the name of the particle", "the name of the particle", conf_err_prefix(chain, node));
conf_err_prefix(chain, node));
return false; return false;
} }
@ -264,7 +240,8 @@ conf_verify_decoration(keychain_t *chain, const struct yml_node *node)
const struct deco_iface *iface = plugin_load_deco(deco_name); const struct deco_iface *iface = plugin_load_deco(deco_name);
if (iface == NULL) { 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; return false;
} }
@ -281,7 +258,10 @@ conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node)
{ {
assert(yml_is_list(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)) if (!conf_verify_particle(chain, it.node))
return false; return false;
} }
@ -296,8 +276,7 @@ conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node)
if (yml_dict_length(node) != 1) { if (yml_dict_length(node) != 1) {
LOG_ERR("%s: particle must be a dictionary with a single key; " LOG_ERR("%s: particle must be a dictionary with a single key; "
"the name of the particle", "the name of the particle", conf_err_prefix(chain, node));
conf_err_prefix(chain, node));
return false; return false;
} }
@ -313,7 +292,8 @@ conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node)
const struct particle_iface *iface = plugin_load_particle(particle_name); const struct particle_iface *iface = plugin_load_particle(particle_name);
if (iface == NULL) { 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; return false;
} }
@ -335,18 +315,19 @@ conf_verify_particle(keychain_t *chain, const struct yml_node *node)
else if (yml_is_list(node)) else if (yml_is_list(node))
return conf_verify_particle_list_items(chain, node); return conf_verify_particle_list_items(chain, node);
else { 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; return false;
} }
} }
static bool static bool
verify_module(keychain_t *chain, const struct yml_node *node) verify_module(keychain_t *chain, const struct yml_node *node)
{ {
if (!yml_is_dict(node) || yml_dict_length(node) != 1) { if (!yml_is_dict(node) || yml_dict_length(node) != 1) {
LOG_ERR("%s: module must be a dictionary with a single key; " LOG_ERR("%s: module must be a dictionary with a single key; "
"the name of the module", "the name of the module", conf_err_prefix(chain, node));
conf_err_prefix(chain, node));
return false; return false;
} }
@ -362,7 +343,8 @@ verify_module(keychain_t *chain, const struct yml_node *node)
const struct module_iface *iface = plugin_load_module(mod_name); const struct module_iface *iface = plugin_load_module(mod_name);
if (iface == NULL) { 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; return false;
} }
@ -384,7 +366,10 @@ verify_module_list(keychain_t *chain, const struct yml_node *node)
return false; 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)) if (!verify_module(chain, it.node))
return false; return false;
} }
@ -396,12 +381,18 @@ static bool
verify_bar_border(keychain_t *chain, const struct yml_node *node) verify_bar_border(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"width", false, &conf_verify_unsigned}, {"left-width", false, &conf_verify_unsigned}, {"width", false, &conf_verify_int},
{"right-width", false, &conf_verify_unsigned}, {"top-width", false, &conf_verify_unsigned}, {"left-width", false, &conf_verify_int},
{"bottom-width", false, &conf_verify_unsigned}, {"color", false, &conf_verify_color}, {"right-width", false, &conf_verify_int},
{"margin", false, &conf_verify_unsigned}, {"left-margin", false, &conf_verify_unsigned}, {"top-width", false, &conf_verify_int},
{"right-margin", false, &conf_verify_unsigned}, {"top-margin", false, &conf_verify_unsigned}, {"bottom-width", false, &conf_verify_int},
{"bottom-margin", false, &conf_verify_unsigned}, {NULL, false, NULL}, {"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); return conf_verify_dict(chain, node, attrs);
@ -416,7 +407,7 @@ verify_bar_location(keychain_t *chain, const struct yml_node *node)
static bool static bool
verify_bar_layer(keychain_t *chain, const struct yml_node *node) verify_bar_layer(keychain_t *chain, const struct yml_node *node)
{ {
return conf_verify_enum(chain, node, (const char *[]){"overlay", "top", "bottom", "background"}, 4); return conf_verify_enum(chain, node, (const char *[]){"top", "bottom"}, 2);
} }
bool bool
@ -431,31 +422,30 @@ conf_verify_bar(const struct yml_node *bar)
chain_push(&chain, "bar"); chain_push(&chain, "bar");
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"height", true, &conf_verify_unsigned}, {"height", true, &conf_verify_int},
{"location", true, &verify_bar_location}, {"location", true, &verify_bar_location},
{"background", true, &conf_verify_color}, {"background", true, &conf_verify_color},
{"monitor", false, &conf_verify_string}, {"monitor", false, &conf_verify_string},
{"layer", false, &verify_bar_layer}, {"layer", false, &verify_bar_layer},
{"spacing", false, &conf_verify_unsigned}, {"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_int},
{"margin", false, &conf_verify_unsigned}, {"margin", false, &conf_verify_int},
{"left-margin", false, &conf_verify_unsigned}, {"left_margin", false, &conf_verify_int},
{"right-margin", false, &conf_verify_unsigned}, {"right_margin", false, &conf_verify_int},
{"border", false, &verify_bar_border}, {"border", false, &verify_bar_border},
{"font", false, &conf_verify_font}, {"font", false, &conf_verify_font},
{"font-shaping", false, &conf_verify_font_shaping},
{"foreground", false, &conf_verify_color}, {"foreground", false, &conf_verify_color},
{"left", false, &verify_module_list}, {"left", false, &verify_module_list},
{"center", false, &verify_module_list}, {"center", false, &verify_module_list},
{"right", false, &verify_module_list}, {"right", false, &verify_module_list},
{"trackpad-sensitivity", false, &conf_verify_unsigned}, {"trackpad-sensitivity", false, &conf_verify_int},
{NULL, false, NULL}, {NULL, false, NULL},
}; };

View file

@ -26,14 +26,16 @@ chain_pop(keychain_t *chain)
tll_pop_back(*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_string(keychain_t *chain, const struct yml_node *node);
bool conf_verify_int(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_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 conf_verify_list(keychain_t *chain, const struct yml_node *node,
bool (*verify)(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, bool conf_verify_dict(keychain_t *chain, const struct yml_node *node,
@ -42,7 +44,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_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_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(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(keychain_t *chain, const struct yml_node *node);
bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node); bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node);

216
config.c
View file

@ -1,10 +1,9 @@
#include "config.h" #include "config.h"
#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <dlfcn.h> #include <dlfcn.h>
@ -21,7 +20,9 @@
static uint8_t static uint8_t
hex_nibble(char hex) 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') if (hex >= '0' && hex <= '9')
return hex - '0'; return hex - '0';
@ -55,9 +56,9 @@ conf_to_color(const struct yml_node *node)
alpha |= alpha << 8; alpha |= alpha << 8;
return (pixman_color_t){ 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, .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, .alpha = alpha,
}; };
} }
@ -65,69 +66,7 @@ conf_to_color(const struct yml_node *node)
struct fcft_font * struct fcft_font *
conf_to_font(const struct yml_node *node) conf_to_font(const struct yml_node *node)
{ {
const char *font_spec = yml_value_as_string(node); return fcft_from_name(1, &(const char *){yml_value_as_string(node)}, NULL);
size_t count = 0;
size_t size = 0;
const char **fonts = NULL;
char *copy = strdup(font_spec);
for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) {
/* Trim spaces, strictly speaking not necessary, but looks nice :) */
while (isspace(font[0]))
font++;
if (font[0] == '\0')
continue;
if (count + 1 > size) {
size += 4;
fonts = realloc(fonts, size * sizeof(fonts[0]));
}
assert(count + 1 <= size);
fonts[count++] = font;
}
struct fcft_font *ret = fcft_from_name(count, fonts, NULL);
free(fonts);
free(copy);
return ret;
}
enum font_shaping
conf_to_font_shaping(const struct yml_node *node)
{
const char *v = yml_value_as_string(node);
if (strcmp(v, "none") == 0)
return FONT_SHAPE_NONE;
else if (strcmp(v, "graphemes") == 0) {
static bool have_warned = false;
if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_GRAPHEME_SHAPING)) {
have_warned = true;
LOG_WARN("cannot enable grapheme shaping; no support in fcft");
}
return FONT_SHAPE_GRAPHEMES;
}
else if (strcmp(v, "full") == 0) {
static bool have_warned = false;
if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING)) {
have_warned = true;
LOG_WARN("cannot enable full text shaping; no support in fcft");
}
return FONT_SHAPE_FULL;
}
else {
assert(false);
return FONT_SHAPE_NONE;
}
} }
struct deco * struct deco *
@ -145,20 +84,25 @@ conf_to_deco(const struct yml_node *node)
} }
static struct particle * 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); size_t count = yml_list_length(node);
struct particle *parts[count]; struct particle *parts[count];
size_t idx = 0; 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); parts[idx] = conf_to_particle(it.node, inherited);
} }
/* Lazy-loaded function pointer to particle_list_new() */ /* Lazy-loaded function pointer to particle_list_new() */
static struct particle *(*particle_list_new)(struct particle *common, struct particle *particles[], size_t count, static struct particle *(*particle_list_new)(
int left_spacing, int right_spacing) struct particle *common,
= NULL; struct particle *particles[], size_t count,
int left_spacing, int right_spacing) = NULL;
if (particle_list_new == NULL) { if (particle_list_new == NULL) {
const struct plugin *plug = plugin_load("list", PLUGIN_PARTICLE); 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); assert(particle_list_new != NULL);
} }
struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping, struct particle *common = particle_common_new(
inherited.foreground, NULL); 0, 0, NULL, fcft_clone(inherited.font), inherited.foreground, NULL);
return particle_list_new(common, parts, count, 0, 2); 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 *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 *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_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 *foreground_node = yml_get_value(pair.value, "foreground");
const struct yml_node *deco_node = yml_get_value(pair.value, "deco"); 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 left = margin != NULL ? yml_value_as_int(margin) :
int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0; 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) { if (on_click != NULL) {
const char *yml_legacy = yml_value_as_string(on_click); const char *legacy = yml_value_as_string(on_click);
if (yml_legacy != NULL) {
char *legacy = NULL;
if (yml_legacy[0] == '~' && yml_legacy[1] == '/') {
const char *home_dir = getenv("HOME");
if (home_dir != NULL)
if (asprintf(&legacy, "%s/%s", home_dir, yml_legacy + 2) < 0)
legacy = NULL;
if (legacy == NULL)
legacy = strdup(yml_legacy);
} else
legacy = strdup(yml_legacy);
if (legacy != NULL)
on_click_templates[MOUSE_BTN_LEFT] = legacy; on_click_templates[MOUSE_BTN_LEFT] = legacy;
}
else if (yml_is_dict(on_click)) { if (yml_is_dict(on_click)) {
for (struct yml_dict_iter it = yml_dict_iter(on_click); it.key != NULL; yml_dict_next(&it)) { 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 *key = yml_value_as_string(it.key);
const char *yml_template = yml_value_as_string(it.value); const char *template = yml_value_as_string(it.value);
char *template = NULL;
if (yml_template[0] == '~' && yml_template[1] == '/') {
const char *home_dir = getenv("HOME");
if (home_dir != NULL)
if (asprintf(&template, "%s/%s", home_dir, yml_template + 2) < 0)
template = NULL;
if (template == NULL)
template = strdup(yml_template);
} else
template = strdup(yml_template);
if (strcmp(key, "left") == 0) if (strcmp(key, "left") == 0)
on_click_templates[MOUSE_BTN_LEFT] = template; 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; on_click_templates[MOUSE_BTN_WHEEL_UP] = template;
else if (strcmp(key, "wheel-down") == 0) else if (strcmp(key, "wheel-down") == 0)
on_click_templates[MOUSE_BTN_WHEEL_DOWN] = template; 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 else
assert(false); 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 * clone the font, since each particle takes ownership of its own
* font. * font.
*/ */
struct fcft_font *font = font_node != NULL ? conf_to_font(font_node) : fcft_clone(inherited.font); struct fcft_font *font = font_node != NULL
enum font_shaping font_shaping ? conf_to_font(font_node) : fcft_clone(inherited.font);
= font_shaping_node != NULL ? conf_to_font_shaping(font_shaping_node) : inherited.font_shaping; pixman_color_t foreground = foreground_node != NULL
pixman_color_t foreground = foreground_node != NULL ? conf_to_color(foreground_node) : inherited.foreground; ? conf_to_color(foreground_node) : inherited.foreground;
/* Instantiate base/common particle */ /* Instantiate base/common particle */
struct particle *common struct particle *common = particle_common_new(
= particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco); left, right, on_click_templates, font, foreground, deco);
const struct particle_iface *iface = plugin_load_particle(type); const struct particle_iface *iface = plugin_load_particle(type);
@ -290,7 +205,6 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
struct bar_config conf = { struct bar_config conf = {
.backend = backend, .backend = backend,
.layer = BAR_LAYER_BOTTOM, .layer = BAR_LAYER_BOTTOM,
.font_shaping = FONT_SHAPE_FULL,
}; };
/* /*
@ -301,7 +215,8 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
conf.height = yml_value_as_int(height); conf.height = yml_value_as_int(height);
const struct yml_node *location = yml_get_value(bar, "location"); 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"); const struct yml_node *background = yml_get_value(bar, "background");
conf.background = conf_to_color(background); conf.background = conf_to_color(background);
@ -317,18 +232,15 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
const struct yml_node *layer = yml_get_value(bar, "layer"); const struct yml_node *layer = yml_get_value(bar, "layer");
if (layer != NULL) { if (layer != NULL) {
const char *tmp = yml_value_as_string(layer); const char *tmp = yml_value_as_string(layer);
if (strcmp(tmp, "overlay") == 0) if (strcmp(tmp, "top") == 0)
conf.layer = BAR_LAYER_OVERLAY;
else if (strcmp(tmp, "top") == 0)
conf.layer = BAR_LAYER_TOP; conf.layer = BAR_LAYER_TOP;
else if (strcmp(tmp, "bottom") == 0) else if (strcmp(tmp, "bottom") == 0)
conf.layer = BAR_LAYER_BOTTOM; conf.layer = BAR_LAYER_BOTTOM;
else if (strcmp(tmp, "background") == 0)
conf.layer = BAR_LAYER_BACKGROUND;
else else
assert(false); assert(false);
} }
const struct yml_node *spacing = yml_get_value(bar, "spacing"); const struct yml_node *spacing = yml_get_value(bar, "spacing");
if (spacing != NULL) if (spacing != NULL)
conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing); conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing);
@ -353,8 +265,11 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
if (right_margin != NULL) if (right_margin != NULL)
conf.right_margin = yml_value_as_int(right_margin); conf.right_margin = yml_value_as_int(right_margin);
const struct yml_node *trackpad_sensitivity = yml_get_value(bar, "trackpad-sensitivity"); const struct yml_node *trackpad_sensitivity =
conf.trackpad_sensitivity = trackpad_sensitivity != NULL ? yml_value_as_int(trackpad_sensitivity) : 30; 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"); const struct yml_node *border = yml_get_value(bar, "border");
if (border != NULL) { if (border != NULL) {
@ -371,8 +286,10 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
const struct yml_node *bottom_margin = yml_get_value(border, "bottom-margin"); const struct yml_node *bottom_margin = yml_get_value(border, "bottom-margin");
if (width != NULL) if (width != NULL)
conf.border.left_width = conf.border.right_width = conf.border.top_width = conf.border.bottom_width conf.border.left_width =
= yml_value_as_int(width); conf.border.right_width =
conf.border.top_width =
conf.border.bottom_width = yml_value_as_int(width);
if (left_width != NULL) if (left_width != NULL)
conf.border.left_width = yml_value_as_int(left_width); conf.border.left_width = yml_value_as_int(left_width);
@ -387,8 +304,10 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
conf.border.color = conf_to_color(color); conf.border.color = conf_to_color(color);
if (margin != NULL) if (margin != NULL)
conf.border.left_margin = conf.border.right_margin = conf.border.top_margin = conf.border.bottom_margin conf.border.left_margin =
= yml_value_as_int(margin); conf.border.right_margin =
conf.border.top_margin =
conf.border.bottom_margin = yml_value_as_int(margin);
if (left_margin != NULL) if (left_margin != NULL)
conf.border.left_margin = yml_value_as_int(left_margin); conf.border.left_margin = yml_value_as_int(left_margin);
@ -408,7 +327,6 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
* foreground color at top-level. * foreground color at top-level.
*/ */
struct fcft_font *font = fcft_from_name(1, &(const char *){"sans"}, NULL); struct fcft_font *font = fcft_from_name(1, &(const char *){"sans"}, NULL);
enum font_shaping font_shaping = FONT_SHAPE_FULL;
pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */ pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */
const struct yml_node *font_node = yml_get_value(bar, "font"); const struct yml_node *font_node = yml_get_value(bar, "font");
@ -417,17 +335,12 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
font = conf_to_font(font_node); 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"); const struct yml_node *foreground_node = yml_get_value(bar, "foreground");
if (foreground_node != NULL) if (foreground_node != NULL)
foreground = conf_to_color(foreground_node); foreground = conf_to_color(foreground_node);
struct conf_inherit inherited = { struct conf_inherit inherited = {
.font = font, .font = font,
.font_shaping = font_shaping,
.foreground = foreground, .foreground = foreground,
}; };
@ -443,7 +356,10 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
struct module **mods = calloc(count, sizeof(*mods)); struct module **mods = calloc(count, sizeof(*mods));
size_t idx = 0; 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); struct yml_dict_iter m = yml_dict_iter(it.node);
const char *mod_name = yml_value_as_string(m.key); const char *mod_name = yml_value_as_string(m.key);
@ -454,14 +370,14 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
* applied to all its particles. * applied to all its particles.
*/ */
const struct yml_node *mod_font = yml_get_value(m.value, "font"); 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(
const struct yml_node *mod_foreground = yml_get_value(m.value, "foreground"); m.value, "foreground");
struct conf_inherit mod_inherit = { struct conf_inherit mod_inherit = {
.font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font, .font = mod_font != NULL
.font_shaping ? conf_to_font(mod_font) : inherited.font,
= mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping, .foreground = mod_foreground != NULL
.foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground, ? conf_to_color(mod_foreground) : inherited.foreground,
}; };
const struct module_iface *iface = plugin_load_module(mod_name); const struct module_iface *iface = plugin_load_module(mod_name);

View file

@ -1,9 +1,8 @@
#pragma once #pragma once
#include "bar/bar.h"
#include "font-shaping.h"
#include "yml.h"
#include <fcft/fcft.h> #include <fcft/fcft.h>
#include "yml.h"
#include "bar/bar.h"
struct bar; struct bar;
struct particle; 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); pixman_color_t conf_to_color(const struct yml_node *node);
struct fcft_font *conf_to_font(const struct yml_node *node); struct fcft_font *conf_to_font(const struct yml_node *node);
enum font_shaping conf_to_font_shaping(const struct yml_node *node);
struct conf_inherit { struct conf_inherit {
const struct fcft_font *font; const struct fcft_font *font;
enum font_shaping font_shaping;
pixman_color_t foreground; 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); struct deco *conf_to_deco(const struct yml_node *node);

View file

@ -4,11 +4,10 @@
struct deco { struct deco {
void *private; 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); void (*destroy)(struct deco *deco);
}; };
#define DECORATION_COMMON_ATTRS \ #define DECORATION_COMMON_ATTRS \
{ \ {NULL, false, NULL}
NULL, false, NULL \
}

View file

@ -1,13 +1,12 @@
#include <stdlib.h> #include <stdlib.h>
#include "../config-verify.h"
#include "../config.h" #include "../config.h"
#include "../config-verify.h"
#include "../decoration.h" #include "../decoration.h"
#include "../plugin.h" #include "../plugin.h"
struct private struct private {
{ //struct rgba color;
// struct rgba color;
pixman_color_t 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) expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
{ {
const struct private *d = deco->private; 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 * 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]) deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
decorations = [] decorations = []
foreach deco : ['background', 'border', 'stack', 'underline', 'overline'] foreach deco : ['background', 'stack', 'underline']
if plugs_as_libs if plugs_as_libs
shared_module('@0@'.format(deco), '@0@.c'.format(deco), shared_module('@0@'.format(deco), '@0@.c'.format(deco),
dependencies: deco_sdk, 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> #include <stdlib.h>
#define LOG_MODULE "stack" #define LOG_MODULE "stack"
#include "../config-verify.h"
#include "../config.h"
#include "../decoration.h"
#include "../log.h" #include "../log.h"
#include "../config.h"
#include "../config-verify.h"
#include "../decoration.h"
#include "../plugin.h" #include "../plugin.h"
struct private struct private {
{
struct deco **decos; struct deco **decos;
size_t count; size_t count;
}; };
@ -58,7 +57,10 @@ from_conf(const struct yml_node *node)
struct deco *decos[count]; struct deco *decos[count];
size_t idx = 0; 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); decos[idx] = conf_to_deco(it.node);
} }
@ -73,7 +75,10 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
return false; 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)) if (!conf_verify_decoration(chain, it.node))
return false; return false;
} }

View file

@ -1,12 +1,11 @@
#include <stdlib.h> #include <stdlib.h>
#include "../config-verify.h"
#include "../config.h" #include "../config.h"
#include "../config-verify.h"
#include "../decoration.h" #include "../decoration.h"
#include "../plugin.h" #include "../plugin.h"
struct private struct private {
{
int size; int size;
pixman_color_t color; 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) expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
{ {
const struct private *d = deco->private; const struct private *d = deco->private;
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, pixman_image_fill_rectangles(
&(pixman_rectangle16_t){x, y + height - d->size, width, d->size}); PIXMAN_OP_OVER, pix, &d->color, 1,
&(pixman_rectangle16_t){x, y + height - d->size, width, d->size});
} }
static struct deco * static struct deco *
@ -54,7 +54,7 @@ static bool
verify_conf(keychain_t *chain, const struct yml_node *node) verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"size", true, &conf_verify_unsigned}, {"size", true, &conf_verify_int},
{"color", true, &conf_verify_color}, {"color", true, &conf_verify_color},
DECORATION_COMMON_ATTRS, DECORATION_COMMON_ATTRS,
}; };

View file

@ -1,86 +1,19 @@
sh = find_program('sh', native: true) sh = find_program('sh', native: true)
scdoc = dependency('scdoc', 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 = [] foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
if plugin_alsa_enabled 'yambar-modules-alsa.5.scd', 'yambar-modules-backlight.5.scd',
plugin_pages += ['yambar-modules-alsa.5.scd'] 'yambar-modules-battery.5.scd', 'yambar-modules-clock.5.scd',
endif 'yambar-modules-foreign-toplevel.5.scd',
if plugin_backlight_enabled 'yambar-modules-i3.5.scd', 'yambar-modules-label.5.scd',
plugin_pages += ['yambar-modules-backlight.5.scd'] 'yambar-modules-mpd.5.scd', 'yambar-modules-network.5.scd',
endif 'yambar-modules-removables.5.scd', 'yambar-modules-river.5.scd',
if plugin_battery_enabled 'yambar-modules-script.5.scd', 'yambar-modules-sway-xkb.5.scd',
plugin_pages += ['yambar-modules-battery.5.scd'] 'yambar-modules-sway.5.scd', 'yambar-modules-xkb.5.scd',
endif 'yambar-modules-xwindow.5.scd', 'yambar-modules.5.scd',
if plugin_clock_enabled 'yambar-particles.5.scd', 'yambar-tags.5.scd']
plugin_pages += ['yambar-modules-clock.5.scd']
endif
if plugin_cpu_enabled
plugin_pages += ['yambar-modules-cpu.5.scd']
endif
if plugin_disk_io_enabled
plugin_pages += ['yambar-modules-disk-io.5.scd']
endif
if plugin_dwl_enabled
plugin_pages += ['yambar-modules-dwl.5.scd']
endif
if plugin_foreign_toplevel_enabled
plugin_pages += ['yambar-modules-foreign-toplevel.5.scd']
endif
if plugin_mem_enabled
plugin_pages += ['yambar-modules-mem.5.scd']
endif
if plugin_mpd_enabled
plugin_pages += ['yambar-modules-mpd.5.scd']
endif
if plugin_mpris_enabled
plugin_pages += ['yambar-modules-mpris.5.scd']
endif
if plugin_i3_enabled
plugin_pages += ['yambar-modules-i3.5.scd']
plugin_pages += ['yambar-modules-sway.5.scd']
endif
if plugin_label_enabled
plugin_pages += ['yambar-modules-label.5.scd']
endif
if plugin_network_enabled
plugin_pages += ['yambar-modules-network.5.scd']
endif
if plugin_niri_language_enabled
plugin_pages += ['yambar-modules-niri-language.5.scd']
endif
if plugin_niri_workspaces_enabled
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
endif
if plugin_pipewire_enabled
plugin_pages += ['yambar-modules-pipewire.5.scd']
endif
if plugin_pulse_enabled
plugin_pages += ['yambar-modules-pulse.5.scd']
endif
if plugin_removables_enabled
plugin_pages += ['yambar-modules-removables.5.scd']
endif
if plugin_river_enabled
plugin_pages += ['yambar-modules-river.5.scd']
endif
if plugin_script_enabled
plugin_pages += ['yambar-modules-script.5.scd']
endif
if plugin_sway_xkb_enabled
plugin_pages += ['yambar-modules-sway-xkb.5.scd']
endif
if plugin_xkb_enabled
plugin_pages += ['yambar-modules-xkb.5.scd']
endif
foreach man_src : ['yambar.1.scd',
'yambar.5.scd',
'yambar-decorations.5.scd',
'yambar-modules.5.scd',
'yambar-particles.5.scd',
'yambar-tags.5.scd'] + plugin_pages
parts = man_src.split('.') parts = man_src.split('.')
name = parts[-3] name = parts[-3]
section = parts[-2] section = parts[-2]
@ -90,7 +23,7 @@ foreach man_src : ['yambar.1.scd',
out, out,
output: out, output: out,
input: man_src, input: man_src,
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())], command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
capture: true, capture: true,
install: true, install: true,
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section))) 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* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| color | color
: color : color
: yes : yes
@ -49,7 +49,7 @@ bottom of the particle.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| size | size
: int : int
: yes : yes
@ -70,74 +70,9 @@ content:
color: ff0000ff 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 # STACK
This particle combines multiple decorations. This particles combines multiple decorations.
## CONFIGURATION ## CONFIGURATION

View file

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

View file

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

View file

@ -7,7 +7,7 @@ clock - This module provides the current date and time
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| time | time
: string : string
: Current time, formatted using the _time-format_ attribute : Current time, formatted using the _time-format_ attribute
@ -20,7 +20,7 @@ clock - This module provides the current date and time
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| time-format | time-format
: string : string
: no : no
@ -29,10 +29,6 @@ clock - This module provides the current date and time
: string : string
: no : no
: *strftime* formatter for the _date_ date (default=*%x*) : *strftime* formatter for the _date_ date (default=*%x*)
| utc
: bool
: no
: Use GMT instead of local timezone (default=false)
# EXAMPLES # 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

@ -21,7 +21,7 @@ Note: Wayland only.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| app-id | app-id
: string : string
: The application ID (typically the application name) : The application ID (typically the application name)
@ -47,7 +47,7 @@ Note: Wayland only.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| content | content
: particle : particle
: yes : yes
@ -67,9 +67,10 @@ bar:
- foreign-toplevel: - foreign-toplevel:
content: content:
map: map:
conditions: tag: activated
~activated: {empty: {}} values:
activated: false: {empty: {}}
true:
- string: {text: "{app-id}: {title}"} - string: {text: "{app-id}: {title}"}
``` ```

View file

@ -22,13 +22,10 @@ with the _application_ and _title_ tags to replace the X11-only
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| name | name
: string : string
: The workspace name : The workspace name
| output
: string
: The output (monitor) the workspace is on
| visible | visible
: bool : bool
: True if the workspace is currently visible (on any output) : 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 | urgent
: bool : bool
: True if the workspace has the urgent flag set : True if the workspace has the urgent flag set
| empty
: bool
: True if the workspace is empty (Sway only)
| state | state
: string : string
: One of *urgent*, *focused*, *unfocused* or *invisible* (note: : One of *urgent*, *focused*, *unfocused* or *invisible* (note:
@ -60,7 +54,7 @@ with the _application_ and _title_ tags to replace the X11-only
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| content | content
: associative array : associative array
: yes : yes
@ -70,11 +64,7 @@ with the _application_ and _title_ tags to replace the X11-only
| sort | sort
: enum : enum
: no : 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. : How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
| 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 | persistent
: list of strings : list of strings
: no : no
@ -105,9 +95,10 @@ bar:
content: content:
"": "":
map: map:
tag: state
default: {string: {text: "{name}"}} default: {string: {text: "{name}"}}
conditions: values:
state == focused: {string: {text: "{name}*"}} focused: {string: {text: "{name}*"}}
current: { string: {text: "{application}: {title}"}} 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* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| state | state
: string : string
: One of *offline*, *stopped*, *paused* or *playing* : 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 | consume
: bool : bool
: True if the *consume* flag is set : True if the *consume* flag is set
| single
: bool
: True if the *single* flag is set
| volume | volume
: range : range
: Volume of MPD in percentage : Volume of MPD in percentage
@ -35,9 +32,6 @@ mpd - This module provides MPD status such as currently playing artist/album/son
| title | title
: string : string
: Title of currently playing song (also valid in *paused* state) : 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 | pos
: string : string
: *%M:%S*-formatted string describing the song's current position : *%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* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| host | host
: string : string
: yes : 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 # DESCRIPTION
This module monitors network connection state; disconnected/connected This module monitors network connection state; disconnected/connected
state and MAC/IP addresses. It instantiates the provided _content_ state and MAC/IP addresses.
particle for each network interface.
Note: while the module internally tracks all assigned IPv4/IPv6 Note: while the module internally tracks all assigned IPv4/IPv6
addresses, it currently exposes only a single IPv4 and a single IPv6 addresses, it currently exposes only a single IPv4 and a single IPv6
address per network interface. address.
# TAGS # TAGS
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| name | name
: string : string
: Network interface name : 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 | index
: int : int
: Network interface index : Network interface index
@ -56,21 +45,12 @@ address per network interface.
| signal | signal
: int : int
: Signal strength, in dBm (Wi-Fi only) : Signal strength, in dBm (Wi-Fi only)
| quality
: range
: Quality of the signal, in percent (Wi-Fi only)
| rx-bitrate | rx-bitrate
: int : int
: RX bitrate, in bits/s : RX bitrate, in Mbit/s
| tx-bitrate | tx-bitrate
: int : int
: TX bitrate in bits/s : TX bitrate in Mbit/s
| dl-speed
: int
: Download speed in bits/s
| ul-speed
: int
: Upload speed in bits/s
# CONFIGURATION # CONFIGURATION
@ -78,46 +58,26 @@ address per network interface.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| left-spacing | name
: int : string
: no : yes
: Space, in pixels, in the left side of each rendered volume : Name of network interface to monitor
| 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 | poll-interval
: int : int
: no : no
: Periodically (in milliseconds) update the signal, quality, rx+tx bitrate, and : Periodically (in seconds) update the signal and rx+tx bitrate tags.
ul+dl speed tags (default=0). Setting it to 0 disables updates. Cannot be less
than 250ms.
# EXAMPLES # EXAMPLES
Display all Ethernet (including WLAN) devices. This excludes loopback,
bridges etc.
``` ```
bar: bar:
left: left:
- network: - network:
name: wlp3s0
content: content:
map: string: {text: "{name}: {state} ({ipv4})"}
conditions:
type == ether || type == wlan:
map:
default:
string: {text: "{name}: {state} ({ipv4})"}
conditions:
ipv4 == "":
string: {text: "{name}: {state}"}
``` ```
# SEE ALSO # 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* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| vendor | vendor
: string : string
: Name of the drive vendor : Name of the drive vendor
@ -22,10 +22,6 @@ instantiates the provided _content_ particle for each detected drive.
| optical | optical
: bool : bool
: True if the drive is an optical drive (CD-ROM, DVD-ROM etc) : 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 | device
: string : string
: Volume device name (typically */dev/sd?*) : Volume device name (typically */dev/sd?*)
@ -48,7 +44,7 @@ instantiates the provided _content_ particle for each detected drive.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| left-spacing | left-spacing
: int : int
: no : no
@ -74,12 +70,13 @@ bar:
- removables: - removables:
content: content:
map: map:
conditions: tag: mounted
~mounted: values:
false:
string: string:
on-click: udisksctl mount -b {device} on-click: udisksctl mount -b {device}
text: "{label}" text: "{label}"
mounted: true:
string: string:
on-click: udisksctl unmount -b {device} on-click: udisksctl unmount -b {device}
text: "{label}" text: "{label}"

View file

@ -13,18 +13,17 @@ It has an interface similar to the i3/sway module.
The configuration for the river module specifies one _title_ particle, The configuration for the river module specifies one _title_ particle,
which will be instantiated once for each seat, with tags representing which will be instantiated once for each seat, with tags representing
the seats' name, the title of the seats' currently focused view, and the seats' name and the title of the seats' currently focused view.
its current river "mode".
It also specifies a _content_ template particle, which is instantiated It also specifies a _content_ template particle, which is instantiated
once for all 32 river tags. This means you probably want to use a once for all 32 river tags. This means you probably want to use a
*map* particle to hide unused river tags. *map* particle to hide unused river tags.
# TAGS (for the "content" particle) # TAGS
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| id | id
: int : int
: River tag number : River tag number
@ -42,35 +41,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). : True if the river tag has views (i.e. windows).
| state | state
: string : string
: Set to *urgent* if _urgent_ is true, *focused* if _focused_ is true, : 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.
*unfocused* if _visible_ is true, but _focused_ is false, or
*invisible* if the river tag is not visible on any monitors.
# TAGS (for the "title" particle)
[[ *Name*
:[ *Type*
:< *Description*
| seat | seat
: string : string
: The name of the seat. : The name of the seat (*title* particle only, see CONFIGURATION)
| title | title
: string : string
: The seat's focused view's title. : The seat's focused view's title (*title* particle only, see CONFIGURATION)
| mode
: string
: The seat's current mode (entered with e.g. *riverctl enter-mode foobar*).
| layout
: string
: Current layout of the output currently focused by the seat.
# CONFIGURATION # CONFIGURATION
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| title | title
: particle : particle
: no : no
@ -92,12 +76,13 @@ once for all 32 river tags. This means you probably want to use a
bar: bar:
left: left:
- river: - river:
title: {string: { text: "{seat} - {title} ({layout}/{mode})" }} title: {string: { text: "{seat} - {title}" }}
content: content:
map: map:
conditions: tag: occupied
~occupied: {empty: {}} values:
occupied: false: {empty: {}}
true:
string: string:
margin: 5 margin: 5
text: "{id}: {state}" 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 continuous mode, the script is executed once. It will typically run
in a loop, sending an updated tag set whenever it needs, or wants 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 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. on non-polling methods to update their state.
Tag sets, or _transactions_, are separated by an empty line Tag sets, or _transactions_, are separated by an empty line
@ -66,21 +66,19 @@ User defined.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| path | path
: string : string
: yes : yes
: Path to script/binary to execute. Must either be an absolute path, : Path to script/binary to execute. Must be an absolute path.
or start with *~/*.
| args | args
: list of strings : list of strings
: no : no
: Arguments to pass to the script/binary. : Arguments to pass to the script/binary.
| poll-interval | poll-interval
: integer : integer
: no : Number of seconds between each script run. If unset, continuous mode
: Number of milliseconds between each script run. If unset, or set to is used.
0, continuous mode is used.
# EXAMPLES # EXAMPLES
@ -115,36 +113,6 @@ bar:
content: {string: {text: "{test}"}} 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 # SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) *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* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| id | id
: string : string
: Input device identifier : Input device identifier
@ -29,7 +29,7 @@ instantiated from this template, and represents an input device.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| identifiers | identifiers
: list of strings : list of strings
: yes : yes

View file

@ -14,7 +14,7 @@ Note: this module is X11 only. It does not work in Wayland.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| name | name
: string : string
: Name of currently selected layout, long version (e.g. "English (US)") : Name of currently selected layout, long version (e.g. "English (US)")

View file

@ -16,7 +16,7 @@ _title_ tags.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:< *Description* :[ *Description*
| application | application
: string : string
: Name of the application that owns the currently focused window : 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: content:
- string: - string:
font: Font Awesome 6 Free:style=solid:pixelsize=14 font: Font Awesome 5 Free:style=solid:pixelsize=14
text:  text: 
- string: - string:
font: Adobe Helvetica:pixelsize=12 font: Adobe Helvetica:pixelsize=12
@ -68,17 +68,20 @@ in red.
``` ```
content: content:
map: map:
conditions: tag: carrier
~carrier: {empty: {}} values:
carrier: false: {empty: {}}
true:
map: map:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}} default: {string: {text: , font: *awesome, foreground: ffffff66}}
conditions: values:
state == up: up:
map: map:
tag: ipv4
default: {string: {text: , font: *awesome}} default: {string: {text: , font: *awesome}}
conditions: values:
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}} "": {string: {text: , font: *awesome, foreground: ffffff66}}
``` ```
## Use yaml anchors ## 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: 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* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| content | content
: particle : particle
: yes : yes
@ -142,28 +145,14 @@ Available modules have their own pages:
*yambar-modules-clock*(5) *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-i3*(5)
*yambar-modules-label*(5) *yambar-modules-label*(5)
*yambar-modules-mem*(5)
*yambar-modules-mpd*(5) *yambar-modules-mpd*(5)
*yambar-modules-network*(5) *yambar-modules-network*(5)
*yambar-modules-pipewire*(5)
*yambar-modules-pulse*(5)
*yambar-modules-removables*(5) *yambar-modules-removables*(5)
*yambar-modules-river*(5) *yambar-modules-river*(5)
@ -174,10 +163,6 @@ Available modules have their own pages:
*yambar-modules-sway*(5) *yambar-modules-sway*(5)
*yambar-modules-niri-language*(5)
*yambar-modules-niri-workspaces*(5)
*yambar-modules-xkb*(5) *yambar-modules-xkb*(5)
*yambar-modules-xwindow*(5) *yambar-modules-xwindow*(5)

View file

@ -12,7 +12,7 @@ following attributes are supported by all particles:
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| left-margin | left-margin
: int : int
: no : 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 : 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 set it on e.g. a _list_ particle, and it will apply to all
particles in the list. 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 | foreground
: color : color
: no : no
: Foreground (text) color. Just like _font_, this is an inherited attribute. : Foreground (text) color. Just like _font_, this is an inherited attribute.
| on-click | 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 : string
: no : no
: Command to execute when the particle is left-clicked. : Command to execute when the particle is clicked. Tags can be
| on-click.right used. Note that the string is *not* executed in a shell.
: string
: no
: Command to execute when the particle is right-clicked.
| on-click.middle
: string
: no
: Command to execute when the particle is middle-clicked.
| on-click.wheel-up
: string
: no
: Command to execute every time a 'wheel-up' event is triggered.
| on-click.wheel-down
: string
: no
: Command to execute every time a 'wheel-down' event is triggered.
| on-click.previous
: string
: no
: Command to execute when the particle is clicked with the 'previous' button.
| on-click.next
: string
: no
: Command to execute when the particle is clicked with the 'next' button.
| deco | deco
: decoration : decoration
: no : no
: Decoration to apply to the particle. See *yambar-decorations*(5) : 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 # STRING
This is the most basic particle. It takes a format string, consisting This is the most basic particle. It takes a format string, consisting
@ -115,7 +59,7 @@ of free text mixed with tag specifiers.
| text | text
: string : string
: yes : 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 have suffixes that can be appended (e.g. _{tag_name:suffix}_). See
*yambar-modules*(5)). *yambar-modules*(5)).
| max | max
@ -123,9 +67,9 @@ of free text mixed with tag specifiers.
: no : no
: Sets the rendered string's maximum length. If the final string's : Sets the rendered string's maximum length. If the final string's
length exceeds this, the rendered string will be truncated, and 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 *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 ## EXAMPLES
@ -155,7 +99,7 @@ content:
This particle is a list (or sequence, if you like) of other This particle is a list (or sequence, if you like) of other
particles. It can be used to render e.g. _string_ particles with 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. combinations.
But note that this means you *cannot* set any attributes on the _list_ But note that this means you *cannot* set any attributes on the _list_
@ -166,7 +110,7 @@ particle itself.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| items | items
: list : list
: yes : yes
@ -214,165 +158,51 @@ content:
- string: ... - 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 # MAP
This particle maps the values of a specific tag to different This particle maps the values of a specific tag to different
particles based on conditions. A condition takes either the form of: particles. In addition to explicit tag values, you can also specify a
```
<tag> <operation> <value>
```
Or, for boolean tags:
```
<tag>
```
Where <tag> is the tag you would like to map, <operation> is one of:
[- ==
:- !=
:- >=
:- >
:- <=
:- <
and <value> is the value you would like to compare it to. *If the
value contains any non-alphanumerical characters, you must
surround it with ' \" ' *:
```
"hello world"
"@#$%"
```
Negation is done with a preceding '~':
```
~<tag>
~<condition>
```
To match for empty strings, use ' "" ':
```
<tag> == ""
```
String glob matching
To perform string matching using globbing with "\*" & "?" characters:
\* Match any zero or more characters. ? Match exactly any one
character.
```
<tag> ~~ "hello*"
```
Will match any string starting with "hello", including "hello",
"hello1", "hello123", etc.
```
<tag> ~~ "hello?"
```
Will match any string starting with "hello" followed by any single
character, including "hello1", "hello-", but not "hello".
Furthermore, you may use the boolean operators:
[- &&
:- ||
in order to create more complex conditions:
```
<condition1> && <condition2>
```
You may surround <condition> with parenthesis for clarity or
specifying precedence:
```
(<condition>)
<condition1> && (<condition2> || <condition3>)
```
In addition to explicit tag values, you can also specify a
default/fallback particle. 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 ## CONFIGURATION
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| conditions | tag
: string
: yes
: The tag (name of) which values should be mapped
| values
: associative array : associative array
: yes : yes
: An associative array of conditions (see above) mapped to particles : An associative array of tag values mapped to particles
| default | default
: particle : particle
: no : 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 ## EXAMPLES
``` ```
content: content:
map: map:
tag: tag_name
default: default:
string: string:
text: this is the default particle; the tag's value is now {tag_name} text: this is the default particle; the tag's value is now {tag_name}
conditions: values:
tag == one_value: one_value:
string: string:
text: tag's value is now one_value text: tag's value is now one_value
tag == another_value: another_value:
string: string:
text: tag's value is now another_value 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 # RAMP
This particle uses a range tag to index into an array of This particle uses a range tag to index into an array of
@ -385,7 +215,7 @@ indicator.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| tag | tag
: string : string
: yes : yes
@ -396,18 +226,6 @@ indicator.
: List of particles. Note that the tag value is *not* used as-is; its : 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 minimum and maximum values are used to map the tag's range to the
particle list's range. 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 ## EXAMPLES
@ -440,7 +258,7 @@ itself when needed.
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| tag | tag
: string : string
: yes : yes
@ -476,7 +294,7 @@ itself when needed.
``` ```
content: content:
progress-bar: progres-bar:
tag: tag_name tag: tag_name
length: 20 length: 20
start: {string: {text: ├}} start: {string: {text: ├}}

View file

@ -11,7 +11,7 @@ their information. Each module defines its own set of tags.
The available tag *types* are: The available tag *types* are:
[[ *Type* [[ *Type*
:< *Description* :[ *Description*
| string | string
: Value is a string. Rendered as-is by the _string_ particle. : Value is a string. Rendered as-is by the _string_ particle.
| int | int
@ -58,77 +58,39 @@ be used.
[[ *Formatter* [[ *Formatter*
:[ *Kind* :[ *Kind*
:[ *Applies to* :[ *Description*
:< *Description* :[ *Applies to*]
| [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 | hex
: format : format
: All tag types
: Renders a tag's value in hex : Renders a tag's value in hex
: All tag types
| oct | oct
: format : format
: All tag types
: Renders a tag's value in octal : Renders a tag's value in octal
: All tag types
| % | %
: format : format
: Range tags
: Renders a range tag's value as a percentage value : Renders a range tag's value as a percentage value
| /N : Range tags
: format
: All tag types
: Renders a tag's value (in decimal) divided by N
| kb, mb, gb | kb, mb, gb
: format : format
: Renders a tag's value (in decimal) divided by 1024, 1024^2 or
1024^3. Note: no unit suffix is appended)
: All tag types : 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 | kib, mib, gib
: format : format
: Same as *kb*, *mb* and *gb*, but divide by 1000^n instead of 1024^n.
: All tag types : All tag types
: Same as *kb*, *mb* and *gb*, but divide by 1024^n instead of 1000^n.
| min | min
: selector : selector
: Range tags
: Renders a range tag's minimum value : Renders a range tag's minimum value
: Range tags
| max | max
: selector : selector
: Range tags
: Renders a range tag's maximum value : Renders a range tag's maximum value
: Range tags
| unit | unit
: selector : selector
: Realtime tags
: Renders a realtime tag's unit (e.g. "s", or "ms") : Renders a realtime tag's unit (e.g. "s", or "ms")
: Realtime tags
# 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
```

View file

@ -25,11 +25,11 @@ yambar - modular status panel for X11 and Wayland
*-p*,*--print-pid*=_FILE_|_FD_ *-p*,*--print-pid*=_FILE_|_FD_
Print PID to this file, or FD, when successfully started. The file Print PID to this file, or FD, when successfully started. The file
(or FD) is closed immediately after writing the PID. When a _FILE_ (or FD) is closed immediately after writing the PID. When a _FILE_
as been specified, the file is unlinked upon exiting. as been specified, the file is unlinked exit.
*-d*,*--log-level*={*info*,*warning*,*error*,*none*} *-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as Log level, used both for log output on stderr as well as
syslog. Default: _warning_. syslog. Default: _info_.
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}] *-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
Enables or disables colorization of log output on stderr. 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 Besides the normal yaml types, there are a couple of yambar specific
types that are frequently used: types that are frequently used:
- *font*: this is a comma separated list of fonts in _fontconfig_ - *font*: this is a string in _fontconfig_ format. Example of valid values:
format. Example of valid values: - Font Awesome 5 Brands
- Font Awesome 6 Brands - Font Awesome 5 Free:style=solid
- Font Awesome 6 Free:style=solid
- Dina:pixelsize=10:slant=italic - Dina:pixelsize=10:slant=italic
- Dina:pixelsize=10:weight=bold - Dina:pixelsize=10:weight=bold
- *color*: an rgba hexstring; _RRGGBBAA_. Examples: - *color*: an rgba hexstring; _RRGGBBAA_. Examples:
@ -23,17 +22,12 @@ types that are frequently used:
- 000000ff: black, no transparency - 000000ff: black, no transparency
- 00ff00ff: green, no transparency - 00ff00ff: green, no transparency
- ff000099: red, semi-transparent - 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 # FORMAT
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:< *Description* :[ *Description*
| height | height
: int : int
: yes : yes
@ -54,8 +48,7 @@ types that are frequently used:
| layer | layer
: string : string
: no : no
: Layer to put bar on. One of _overlay_, _top_, _bottom_ or : Layer to put bar on. One of _top_ or _bottom_. Wayland only
_background_. Wayland only. Default: _bottom_.
| left-spacing | left-spacing
: int : int
: no : no
@ -131,17 +124,7 @@ types that are frequently used:
| font | font
: font : font
: no : no
: Default font to use in modules and particles. May also be a comma : Default font to use in modules and particles
separated list of several fonts, in which case the first font is
the primary font, and the rest fallback fonts. These are yambar
custom fallback fonts that will be searched before the fontconfig
provided fallback list.
| font-shaping
: enum
: no
: Default setting for font-shaping, for use in particles. One of
_full_ or _none_. When set to _full_ (the default), strings will be
"shaped" using HarfBuzz. Requires support in fcft.
| foreground | foreground
: color : color
: no : no
@ -178,8 +161,8 @@ bar:
right: right:
- clock: - clock:
content: content:
- string: {text: "{time}"} - string: {text: "{time}"}
``` ```
# FILES # FILES

View file

@ -4,9 +4,9 @@
# For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and # For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and
# the sway-xkb module with the xkb module. # the sway-xkb module with the xkb module.
# fonts we'll be reusing here and there # fonts we'll be re-using here and there
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14 awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
awesome_brands: &awesome_brands Font Awesome 6 Brands:pixelsize=16 awesome_brands: &awesome_brands Font Awesome 5 Brands:pixelsize=16
std_underline: &std_underline {underline: { size: 2, color: ff0000ff}} std_underline: &std_underline {underline: { size: 2, color: ff0000ff}}
@ -46,67 +46,82 @@ bar:
foreground: 000000ff foreground: 000000ff
deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]} deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]}
- map: &i3_mode - map: &i3_mode
tag: mode
default: default:
- string: - string:
margin: 5 margin: 5
text: "{mode}" text: "{mode}"
deco: {background: {color: cc421dff}} deco: {background: {color: cc421dff}}
- empty: {right-margin: 7} - empty: {right-margin: 7}
conditions: values:
mode == default: {empty: {}} default: {empty: {}}
content: content:
"": "":
map: map:
conditions: tag: state
state == focused: {string: {<<: [*default, *focused]}} values:
state == unfocused: {string: {<<: *default}} focused: {string: {<<: [*default, *focused]}}
state == invisible: {string: {<<: [*default, *invisible]}} unfocused: {string: {<<: *default}}
state == urgent: {string: {<<: [*default, *urgent]}} invisible: {string: {<<: [*default, *invisible]}}
urgent: {string: {<<: [*default, *urgent]}}
main: main:
map: map:
conditions: tag: state
state == focused: {string: {<<: [*main, *focused]}} values:
state == unfocused: {string: {<<: *main}} focused: {string: {<<: [*main, *focused]}}
state == invisible: {string: {<<: [*main, *invisible]}} unfocused: {string: {<<: *main}}
state == urgent: {string: {<<: [*main, *urgent]}} invisible: {string: {<<: [*main, *invisible]}}
urgent: {string: {<<: [*main, *urgent]}}
surfing: surfing:
map: map:
conditions: tag: state
state == focused: {string: {<<: [*surfing, *focused]}} values:
state == unfocused: {string: {<<: *surfing}} focused: {string: {<<: [*surfing, *focused]}}
state == invisible: {string: {<<: [*surfing, *invisible]}} unfocused: {string: {<<: *surfing}}
state == urgent: {string: {<<: [*surfing, *urgent]}} invisible: {string: {<<: [*surfing, *invisible]}}
urgent: {string: {<<: [*surfing, *urgent]}}
misc: misc:
map: map:
conditions: tag: state
state == focused: {string: {<<: [*misc, *focused]}} values:
state == unfocused: {string: {<<: *misc}} focused: {string: {<<: [*misc, *focused]}}
state == invisible: {string: {<<: [*misc, *invisible]}} unfocused: {string: {<<: *misc}}
state == urgent: {string: {<<: [*misc, *urgent]}} invisible: {string: {<<: [*misc, *invisible]}}
urgent: {string: {<<: [*misc, *urgent]}}
mail: mail:
map: map:
conditions: tag: state
state == focused: {string: {<<: [*mail, *focused]}} values:
state == unfocused: {string: {<<: *mail}} focused: {string: {<<: [*mail, *focused]}}
state == invisible: {string: {<<: [*mail, *invisible]}} unfocused: {string: {<<: *mail}}
state == urgent: {string: {<<: [*mail, *urgent]}} invisible: {string: {<<: [*mail, *invisible]}}
urgent: {string: {<<: [*mail, *urgent]}}
music: music:
map: map:
conditions: tag: state
state == focused: {string: {<<: [*music, *focused]}} values:
state == unfocused: {string: {<<: *music}} focused: {string: {<<: [*music, *focused]}}
state == invisible: {string: {<<: [*music, *invisible]}} unfocused: {string: {<<: *music}}
state == urgent: {string: {<<: [*music, *urgent]}} 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: center:
- mpd: - mpd:
host: /run/mpd/socket host: /run/mpd/socket
@ -115,28 +130,32 @@ bar:
spacing: 0 spacing: 0
items: items:
- map: - map:
conditions: tag: state
state == playing: {string: {text: "{artist}"}} values:
state == paused: {string: {text: "{artist}", foreground: ffffff66}} playing: {string: {text: "{artist}"}}
paused: {string: {text: "{artist}", foreground: ffffff66}}
- string: {text: " | ", foreground: ffffff66} - string: {text: " | ", foreground: ffffff66}
- map: - map:
conditions: tag: state
state == playing: {string: {text: "{album}"}} values:
state == paused: {string: {text: "{album}", foreground: ffffff66}} playing: {string: {text: "{album}"}}
paused: {string: {text: "{album}", foreground: ffffff66}}
- string: {text: " | ", foreground: ffffff66} - string: {text: " | ", foreground: ffffff66}
- map: - map:
conditions: tag: state
state == playing: {string: {text: "{title}", foreground: ffa0a0ff}} values:
state == paused: {string: {text: "{title}", foreground: ffffff66}} playing: {string: {text: "{title}", foreground: ffa0a0ff}}
paused: {string: {text: "{title}", foreground: ffffff66}}
content: content:
map: map:
margin: 10 margin: 10
conditions: tag: state
state == offline: {string: {text: offline, foreground: ff0000ff}} values:
state == stopped: {string: {text: stopped}} offline: {string: {text: offline, foreground: ff0000ff}}
state == paused: {list: *artist_album_title} stopped: {string: {text: stopped}}
state == playing: {list: *artist_album_title} paused: {list: *artist_album_title}
playing: {list: *artist_album_title}
right: right:
- removables: - removables:
@ -146,21 +165,24 @@ bar:
spacing: 5 spacing: 5
content: content:
map: map:
conditions: tag: mounted
~mounted: values:
false:
map: map:
tag: optical
on-click: udisksctl mount -b {device} on-click: udisksctl mount -b {device}
conditions: values:
~optical: [{string: *drive}, {string: {text: "{label}"}}] false: [{string: *drive}, {string: {text: "{label}"}}]
optical: [{string: *optical}, {string: {text: "{label}"}}] true: [{string: *optical}, {string: {text: "{label}"}}]
mounted: true:
map: map:
tag: optical
on-click: udisksctl unmount -b {device} on-click: udisksctl unmount -b {device}
conditions: values:
~optical: false:
- string: {<<: *drive, deco: *std_underline} - string: {<<: *drive, deco: *std_underline}
- string: {text: "{label}"} - string: {text: "{label}"}
optical: true:
- string: {<<: *optical, deco: *std_underline} - string: {<<: *optical, deco: *std_underline}
- string: {text: "{label}"} - string: {text: "{label}"}
- sway-xkb: - sway-xkb:
@ -169,69 +191,75 @@ bar:
- string: {text: , font: *awesome} - string: {text: , font: *awesome}
- string: {text: "{layout}"} - string: {text: "{layout}"}
- network: - network:
name: enp1s0
content: content:
map: map:
default: {empty: {}} tag: carrier
conditions: values:
name == enp1s0: false: {empty: {}}
true:
map: map:
conditions: tag: state
~carrier: {empty: {}} default: {string: {text: , font: *awesome, foreground: ffffff66}}
carrier: values:
up:
map: map:
default: {string: {text: , font: *awesome, foreground: ffffff66}} tag: ipv4
conditions: default: {string: {text: , font: *awesome}}
state == up && ipv4 != "": {string: {text: , font: *awesome}} values:
"": {string: {text: , font: *awesome, foreground: ffffff66}}
- network: - network:
poll-interval: 1000 name: wlp2s0
content: content:
map: map:
default: {empty: {}} tag: state
conditions: default: {string: {text: , font: *awesome, foreground: ffffff66}}
name == wlp2s0: values:
down: {string: {text: , font: *awesome, foreground: ff0000ff}}
up:
map: map:
default: {string: {text: , font: *awesome, foreground: ffffff66}} tag: ipv4
conditions: default:
state == down: {string: {text: , font: *awesome, foreground: ff0000ff}} - string: {text: , font: *awesome}
state == up: - string: {text: "{ssid}"}
map: values:
default: "":
- string: {text: , font: *awesome} - string: {text: , font: *awesome, foreground: ffffff66}
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s"} - string: {text: "{ssid}", foreground: ffffff66}
conditions:
ipv4 == "":
- string: {text: , font: *awesome, foreground: ffffff66}
- string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s", foreground: ffffff66}
- alsa: - alsa:
card: hw:PCH card: hw:PCH
mixer: Master mixer: Master
content: content:
map: map:
conditions: tag: online
~online: {string: {text: , font: *awesome, foreground: ff0000ff}} values:
online: false: {string: {text: , font: *awesome, foreground: ff0000ff}}
true:
map: map:
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle" on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
conditions: tag: muted
muted: {string: {text: , font: *awesome, foreground: ffffff66}} values:
~muted: true: {string: {text: , font: *awesome, foreground: ffffff66}}
false:
ramp: ramp:
tag: percent tag: volume
items: items:
- string: {text: , font: *awesome} - string: {text: , font: *awesome}
- string: {text: , font: *awesome} - string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- string: {text: , font: *awesome}
- string: {text: , font: *awesome} - string: {text: , font: *awesome}
- backlight: - backlight:
name: intel_backlight name: intel_backlight
content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}] content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}]
- battery: - battery:
name: BAT0 name: BAT0
poll-interval: 30000 poll-interval: 30
anchors: content:
discharging: &discharging map:
list: tag: state
items: values:
discharging:
- ramp: - ramp:
tag: capacity tag: capacity
items: items:
@ -246,20 +274,13 @@ bar:
- string: {text: , font: *awesome} - string: {text: , font: *awesome}
- string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% {estimate}"} - string: {text: "{capacity}% {estimate}"}
content: charging:
map:
conditions:
state == unknown:
<<: *discharging
state == discharging:
<<: *discharging
state == charging:
- string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% {estimate}"} - string: {text: "{capacity}% {estimate}"}
state == full: full:
- string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% full"} - string: {text: "{capacity}% full"}
state == "not charging": not charging:
- ramp: - ramp:
tag: capacity tag: capacity
items: items:
@ -284,6 +305,6 @@ bar:
- label: - label:
content: content:
string: string:
on-click: systemctl poweroff on-click: loginctl poweroff
text:  text: 
font: *awesome 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 # Now the fun part
# #
# Example configuration: # Exemple configuration:
# #
# - script: # - script:
# path: /absolute/path/to/dwl-tags.sh # path: /absolute/path/to/dwl-tags.sh
@ -31,33 +31,39 @@
# content: # content:
# - map: # - map:
# margin: 4 # margin: 4
# conditions: # tag: tag_0_occupied
# tag_0_occupied: # values:
# true:
# map: # map:
# conditions: # tag: tag_0_focused
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}} # values:
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *occupied}} # true: {string: {text: "{tag_0}", <<: *focused}}
# ~tag_0_occupied: # false: {string: {text: "{tag_0}", <<: *occupied}}
# false:
# map: # map:
# conditions: # tag: tag_0_focused
# tag_0_focused: {string: {text: "{tag_0}", <<: *focused}} # values:
# ~tag_0_focused: {string: {text: "{tag_0}", <<: *default}} # true: {string: {text: "{tag_0}", <<: *focused}}
# false: {string: {text: "{tag_0}", <<: *default}}
# ... # ...
# ... # ...
# ... # ...
# - map: # - map:
# margin: 4 # margin: 4
# conditions: # tag: tag_8_occupied
# tag_8_occupied: # values:
# map: # true:
# conditions:
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}}
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *occupied}}
# ~tag_8_occupied:
# map: # map:
# tag: tag_8_focused
# values: # values:
# tag_8_focused: {string: {text: "{tag_8}", <<: *focused}} # true: {string: {text: "{tag_8}", <<: *focused}}
# ~tag_8_focused: {string: {text: "{tag_8}", <<: *default}} # 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: # - list:
# spacing: 3 # spacing: 3
# items: # items:
@ -121,7 +127,7 @@ while true; do
inotifywait -qq --event modify "${fname}" inotifywait -qq --event modify "${fname}"
# Get info from the file # Get info from the file
output="$(tail -n6 "${fname}")" output="$(tail -n4 "${fname}")"
title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )" title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )"
#selmon="$(echo "${output}" | grep 'selmon')" #selmon="$(echo "${output}" | grep 'selmon')"
layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )" layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )"
@ -136,3 +142,4 @@ done
unset -v output title layout activetags selectedtags unset -v output title layout activetags selectedtags
unset -v tags name unset -v tags name

View file

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

View file

@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright> </copyright>
<interface name="zriver_status_manager_v1" version="4"> <interface name="zriver_status_manager_v1" version="2">
<description summary="manage river status objects"> <description summary="manage river status objects">
A global factory for objects that receive status information specific A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar. to river. It could be used to implement, for example, a status bar.
@ -47,7 +47,7 @@
</request> </request>
</interface> </interface>
<interface name="zriver_output_status_v1" version="4"> <interface name="zriver_output_status_v1" version="2">
<description summary="track output tags and focus"> <description summary="track output tags and focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
windowing state of an output. windowing state of an output.
@ -83,24 +83,9 @@
</description> </description>
<arg name="tags" type="uint" summary="32-bit bitfield"/> <arg name="tags" type="uint" summary="32-bit bitfield"/>
</event> </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>
<interface name="zriver_seat_status_v1" version="3"> <interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus"> <description summary="track seat focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent focus of a seat. Note that (un)focused_output events will only be sent
@ -136,13 +121,5 @@
</description> </description>
<arg name="title" type="string" summary="title of the focused view"/> <arg name="title" type="string" summary="title of the focused view"/>
</event> </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> </interface>
</protocol> </protocol>

View file

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

View file

@ -13,14 +13,7 @@ out_file=${3}
if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then
workdir=$(pwd) workdir=$(pwd)
cd "${src_dir}" cd "${src_dir}"
git_version=$(git describe --always --tags)
if git describe --tags > /dev/null 2>&1; then
git_version=$(git describe --always --tags)
else
# No tags available, happens in e.g. CI builds
git_version="${default_version}"
fi
git_branch=$(git rev-parse --abbrev-ref HEAD) git_branch=$(git rev-parse --abbrev-ref HEAD)
cd "${workdir}" cd "${workdir}"

51
log.c
View file

@ -1,6 +1,5 @@
#include "log.h" #include "log.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
@ -10,12 +9,13 @@
#include <string.h> #include <string.h>
#include <syslog.h> #include <syslog.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#define ALEN(v) (sizeof(v) / sizeof((v)[0])) #define ALEN(v) (sizeof(v) / sizeof((v)[0]))
#define UNUSED __attribute__((unused)) #define UNUSED __attribute__((unused))
static bool colorize = false; static bool colorize = false;
static bool do_syslog = false; static bool do_syslog = true;
static enum log_class log_level = LOG_CLASS_NONE; static enum log_class log_level = LOG_CLASS_NONE;
static const struct { static const struct {
@ -32,28 +32,21 @@ static const struct {
}; };
void 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 _log_level)
{ {
static const int facility_map[] = { static const int facility_map[] = {
[LOG_FACILITY_USER] = LOG_USER, [LOG_FACILITY_USER] = LOG_USER,
[LOG_FACILITY_DAEMON] = LOG_DAEMON, [LOG_FACILITY_DAEMON] = LOG_DAEMON,
}; };
/* Don't use colors if NO_COLOR is defined and not empty */ colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO);
const char *no_color_str = getenv("NO_COLOR");
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
colorize = _colorize == LOG_COLORIZE_NEVER
? false
: _colorize == LOG_COLORIZE_ALWAYS
? true
: !no_color && isatty(STDERR_FILENO);
do_syslog = _do_syslog; do_syslog = _do_syslog;
log_level = _log_level; log_level = _log_level;
int slvl = log_level_map[_log_level].syslog_equivalent; int slvl = log_level_map[_log_level].syslog_equivalent;
if (do_syslog && slvl != -1) { if (do_syslog && slvl != -1) {
openlog(NULL, /*LOG_PID*/ 0, facility_map[syslog_facility]); openlog(NULL, /*LOG_PID*/0, facility_map[syslog_facility]);
setlogmask(LOG_UPTO(slvl)); setlogmask(LOG_UPTO(slvl));
} }
} }
@ -66,8 +59,8 @@ log_deinit(void)
} }
static void static void
_log(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, int sys_errno, _log(enum log_class log_class, const char *module, const char *file, int lineno,
va_list va) const char *fmt, int sys_errno, va_list va)
{ {
assert(log_class > LOG_CLASS_NONE); assert(log_class > LOG_CLASS_NONE);
assert(log_class < ALEN(log_level_map)); assert(log_class < ALEN(log_level_map));
@ -97,8 +90,9 @@ _log(enum log_class log_class, const char *module, const char *file, int lineno,
} }
static void static void
_sys_log(enum log_class log_class, const char *module, const char UNUSED *file, int UNUSED lineno, const char *fmt, _sys_log(enum log_class log_class, const char *module,
int sys_errno, va_list va) const char UNUSED *file, int UNUSED lineno,
const char *fmt, int sys_errno, va_list va)
{ {
assert(log_class > LOG_CLASS_NONE); assert(log_class > LOG_CLASS_NONE);
assert(log_class < ALEN(log_level_map)); assert(log_class < ALEN(log_level_map));
@ -106,9 +100,6 @@ _sys_log(enum log_class log_class, const char *module, const char UNUSED *file,
if (!do_syslog) if (!do_syslog)
return; return;
if (log_class > log_level)
return;
/* Map our log level to syslog's level */ /* Map our log level to syslog's level */
int level = log_level_map[log_class].syslog_equivalent; int level = log_level_map[log_class].syslog_equivalent;
@ -123,7 +114,8 @@ _sys_log(enum log_class log_class, const char *module, const char UNUSED *file,
} }
void void
log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va) 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_list va2;
va_copy(va2, va); va_copy(va2, va);
@ -133,7 +125,8 @@ log_msg_va(enum log_class log_class, const char *module, const char *file, int l
} }
void void
log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) log_msg(enum log_class log_class, const char *module,
const char *file, int lineno, const char *fmt, ...)
{ {
va_list va; va_list va;
va_start(va, fmt); va_start(va, fmt);
@ -142,13 +135,17 @@ log_msg(enum log_class log_class, const char *module, const char *file, int line
} }
void 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_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); log_errno_provided_va(log_class, module, file, lineno, errno, fmt, va);
} }
void void
log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) log_errno(enum log_class log_class, const char *module,
const char *file, int lineno,
const char *fmt, ...)
{ {
va_list va; va_list va;
va_start(va, fmt); va_start(va, fmt);
@ -157,7 +154,8 @@ log_errno(enum log_class log_class, const char *module, const char *file, int li
} }
void void
log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy, 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) const char *fmt, va_list va)
{ {
va_list va2; va_list va2;
@ -168,7 +166,8 @@ log_errno_provided_va(enum log_class log_class, const char *module, const char *
} }
void void
log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy, log_errno_provided(enum log_class log_class, const char *module,
const char *file, int lineno, int errno_copy,
const char *fmt, ...) const char *fmt, ...)
{ {
va_list va; va_list va;

71
log.h
View file

@ -1,43 +1,68 @@
#pragma once #pragma once
#include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdarg.h>
enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO }; enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO };
enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON }; enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON };
enum log_class { LOG_CLASS_NONE, 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 log_level);
void log_deinit(void); void log_deinit(void);
void log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) void log_msg(
__attribute__((format(printf, 5, 6))); 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, ...) void log_errno(
__attribute__((format(printf, 5, 6))); 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, void log_errno_provided(
const char *fmt, ...) __attribute__((format(printf, 6, 7))); 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)));
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); int log_level_from_string(const char *str);
const char *log_level_string_hint(void); const char *log_level_string_hint(void);
#define LOG_ERR(...) log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #define LOG_ERR(...) \
#define LOG_ERRNO(...) log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERRNO_P(_errno, ...) \ #define LOG_ERRNO(...) \
log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, _errno, __VA_ARGS__) log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...) log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #define LOG_ERRNO_P(_errno, ...) \
#define LOG_INFO(...) log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, \
_errno, __VA_ARGS__)
#define LOG_WARN(...) \
log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) \
log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG #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(...) \
log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__)
#else #else
#define LOG_DBG(...) #define LOG_DBG(...)
#endif #endif

64
main.c
View file

@ -1,6 +1,4 @@
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <locale.h> #include <locale.h>
#include <poll.h> #include <poll.h>
#include <signal.h> #include <signal.h>
@ -11,12 +9,14 @@
#include <string.h> #include <string.h>
#include <threads.h> #include <threads.h>
#include <unistd.h> #include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <fcntl.h> #include <sys/types.h>
#include <pwd.h>
#include <sys/eventfd.h> #include <sys/eventfd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <fcntl.h>
#include <pwd.h>
#include "bar/bar.h" #include "bar/bar.h"
#include "config.h" #include "config.h"
@ -87,7 +87,7 @@ get_config_path(void)
static struct bar * static struct bar *
load_bar(const char *config_path, enum bar_backend backend) 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) { if (conf_file == NULL) {
LOG_ERRNO("%s: failed to open", config_path); LOG_ERRNO("%s: failed to open", config_path);
return NULL; return NULL;
@ -131,7 +131,7 @@ print_usage(const char *prog_name)
" -c,--config=FILE alternative configuration file\n" " -c,--config=FILE alternative configuration file\n"
" -C,--validate verify configuration then quit\n" " -C,--validate verify configuration then quit\n"
" -p,--print-pid=FILE|FD print PID to file or FD\n" " -p,--print-pid=FILE|FD print PID to file or FD\n"
" -d,--log-level={info|warning|error|none} log level (warning)\n" " -d,--log-level={info|warning|error|none} log level (info)\n"
" -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n" " -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n"
" -s,--log-no-syslog disable syslog logging\n" " -s,--log-no-syslog disable syslog logging\n"
" -v,--version show the version number and quit\n"); " -v,--version show the version number and quit\n");
@ -147,8 +147,9 @@ print_pid(const char *pid_file, bool *unlink_at_exit)
int pid_fd = strtoul(pid_file, &end, 10); int pid_fd = strtoul(pid_file, &end, 10);
if (errno != 0 || *end != '\0') { 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)) if ((pid_fd = open(pid_file,
< 0) { 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); LOG_ERRNO("%s: failed to open", pid_file);
return false; return false;
} else } else
@ -177,16 +178,16 @@ int
main(int argc, char *const *argv) main(int argc, char *const *argv)
{ {
static const struct option longopts[] = { static const struct option longopts[] = {
{"backend", required_argument, 0, 'b'}, {"backend", required_argument, 0, 'b'},
{"config", required_argument, 0, 'c'}, {"config", required_argument, 0, 'c'},
{"validate", no_argument, 0, 'C'}, {"validate", no_argument, 0, 'C'},
{"print-pid", required_argument, 0, 'p'}, {"print-pid", required_argument, 0, 'p'},
{"log-level", required_argument, 0, 'd'}, {"log-level", required_argument, 0, 'd'},
{"log-colorize", optional_argument, 0, 'l'}, {"log-colorize", optional_argument, 0, 'l'},
{"log-no-syslog", no_argument, 0, 's'}, {"log-no-syslog", no_argument, 0, 's'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{NULL, no_argument, 0, 0}, {NULL, no_argument, 0, 0},
}; };
bool unlink_pid_file = false; bool unlink_pid_file = false;
@ -196,7 +197,7 @@ main(int argc, char *const *argv)
char *config_path = NULL; char *config_path = NULL;
enum bar_backend backend = BAR_BACKEND_AUTO; enum bar_backend backend = BAR_BACKEND_AUTO;
enum log_class log_level = LOG_CLASS_WARNING; enum log_class log_level = LOG_CLASS_INFO;
enum log_colorize log_colorize = LOG_COLORIZE_AUTO; enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
bool log_syslog = true; bool log_syslog = true;
@ -222,8 +223,9 @@ main(int argc, char *const *argv)
if (stat(optarg, &st) == -1) { if (stat(optarg, &st) == -1) {
fprintf(stderr, "%s: invalid configuration file: %s\n", optarg, strerror(errno)); fprintf(stderr, "%s: invalid configuration file: %s\n", optarg, strerror(errno));
return EXIT_FAILURE; return EXIT_FAILURE;
} else if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) { } else if (!S_ISREG(st.st_mode)) {
fprintf(stderr, "%s: invalid configuration file: neither a regular file nor a pipe or FIFO\n", optarg); fprintf(stderr, "%s: invalid configuration file: not a regular file\n",
optarg);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -242,7 +244,11 @@ main(int argc, char *const *argv)
case 'd': { case 'd': {
int lvl = log_level_from_string(optarg); int lvl = log_level_from_string(optarg);
if (lvl < 0) { if (lvl < 0) {
fprintf(stderr, "-d,--log-level: %s: argument must be one of %s\n", optarg, log_level_string_hint()); fprintf(
stderr,
"-d,--log-level: %s: argument must be one of %s\n",
optarg,
log_level_string_hint());
return EXIT_FAILURE; return EXIT_FAILURE;
} }
log_level = lvl; log_level = lvl;
@ -286,10 +292,12 @@ 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_level);
_Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR, "fcft log level enum offset"); _Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR,
_Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch"); "fcft log level enum offset");
fcft_init((enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level); _Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS,
atexit(&fcft_fini); "fcft colorize enum mismatch");
fcft_log_init(
(enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level);
const struct sigaction sa = {.sa_handler = &signal_handler}; const struct sigaction sa = {.sa_handler = &signal_handler};
sigaction(SIGINT, &sa, NULL); sigaction(SIGINT, &sa, NULL);
@ -366,7 +374,7 @@ main(int argc, char *const *argv)
} }
if (aborted) if (aborted)
LOG_INFO("aborted: %s (%ld)", strsignal(aborted), (long)aborted); LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted);
done: done:
/* Signal abort to other threads */ /* Signal abort to other threads */

View file

@ -1,28 +1,20 @@
project('yambar', 'c', project('yambar', 'c',
version: '1.11.0', version: '1.7.0',
license: 'MIT', license: 'MIT',
meson_version: '>=0.60.0', meson_version: '>=0.53.0',
default_options: ['c_std=c18', default_options: ['c_std=c18',
'warning_level=1', 'warning_level=1',
'werror=true',
'b_ndebug=if-release']) 'b_ndebug=if-release'])
is_debug_build = get_option('buildtype').startswith('debug') is_debug_build = get_option('buildtype').startswith('debug')
plugs_as_libs = get_option('core-plugins-as-shared-libraries') plugs_as_libs = get_option('core-plugins-as-shared-libraries')
cc = meson.get_compiler('c') 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. # Compute the relative path used by compiler invocations.
source_root = meson.current_source_dir().split('/') source_root = meson.current_source_dir().split('/')
build_root = meson.global_build_root().split('/') build_root = meson.build_root().split('/')
relative_dir_parts = [] relative_dir_parts = []
i = 0 i = 0
in_prefix = true in_prefix = true
@ -51,9 +43,7 @@ endif
# Common dependencies # Common dependencies
dl = cc.find_library('dl') dl = cc.find_library('dl')
m = cc.find_library('m') m = cc.find_library('m')
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)] threads = dependency('threads')
libepoll = dependency('epoll-shim', required: false)
libinotify = dependency('libinotify', required: false)
pixman = dependency('pixman-1') pixman = dependency('pixman-1')
yaml = dependency('yaml-0.1') yaml = dependency('yaml-0.1')
@ -75,10 +65,9 @@ backend_wayland = wayland_client.found() and wayland_cursor.found()
# "My" dependencies, fallback to subproject # "My" dependencies, fallback to subproject
tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') 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( add_project_arguments(
cc_flags +
['-D_GNU_SOURCE'] + ['-D_GNU_SOURCE'] +
(is_debug_build ? ['-D_DEBUG'] : []) + (is_debug_build ? ['-D_DEBUG'] : []) +
(backend_x11 ? ['-DENABLE_X11'] : []) + (backend_x11 ? ['-DENABLE_X11'] : []) +
@ -95,20 +84,16 @@ if backend_x11
c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [], c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [],
pic: plugs_as_libs) pic: plugs_as_libs)
xcb_stuff = declare_dependency( xcb_stuff = declare_dependency(link_with: xcb_stuff_lib)
link_with: xcb_stuff_lib,
dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr,
xcb_render, xcb_errors],
)
install_headers('xcb.h', subdir: 'yambar') install_headers('xcb.h', subdir: 'yambar')
endif endif
subdir('completions') subdir('completions')
subdir('doc')
subdir('bar') subdir('bar')
subdir('decorations') subdir('decorations')
subdir('particles') subdir('particles')
subdir('modules') subdir('modules')
subdir('doc')
env = find_program('env', native: true) env = find_program('env', native: true)
generate_version_sh = files('generate-version.sh') generate_version_sh = files('generate-version.sh')
@ -116,16 +101,14 @@ version = custom_target(
'generate_version', 'generate_version',
build_always_stale: true, build_always_stale: true,
output: 'version.h', output: 'version.h',
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@']) command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@SOURCE_ROOT@', '@OUTPUT@'])
yambar = executable( yambar = executable(
'yambar', 'yambar',
'char32.c', 'char32.h',
'color.h', 'color.h',
'config-verify.c', 'config-verify.h', 'config-verify.c', 'config-verify.h',
'config.c', 'config.h', 'config.c', 'config.h',
'decoration.h', 'decoration.h',
'font-shaping.h',
'log.c', 'log.h', 'log.c', 'log.h',
'main.c', 'main.c',
'module.c', 'module.h', 'module.c', 'module.h',
@ -134,7 +117,7 @@ yambar = executable(
'tag.c', 'tag.h', 'tag.c', 'tag.h',
'yml.c', 'yml.h', 'yml.c', 'yml.h',
version, version,
dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] + dependencies: [bar, pixman, yaml, threads, dl, tllist, fcft] +
decorations + particles + modules, decorations + particles + modules,
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles', build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
export_dynamic: true, export_dynamic: true,
@ -170,34 +153,3 @@ summary(
}, },
bool_yn: true 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( option(
'core-plugins-as-shared-libraries', type: 'boolean', value: false, 'core-plugins-as-shared-libraries', type: 'boolean', value: false,
description: 'Compiles modules, particles and decorations as shared libraries, which are loaded on-demand') 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 "module.h"
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
#include <unistd.h> #include <unistd.h>
struct module * struct module *

View file

@ -27,7 +27,7 @@ struct module {
* specified number of milliseconds */ * specified number of milliseconds */
bool (*refresh_in)(struct module *mod, long milli_seconds); 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); 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); struct exposable *module_begin_expose(struct module *mod);
/* List of attributes *all* modules implement */ /* List of attributes *all* modules implement */
#define MODULE_COMMON_ATTRS \ #define MODULE_COMMON_ATTRS \
{"content", true, &conf_verify_particle}, {"anchors", false, NULL}, {"font", false, &conf_verify_font}, \ {"content", true, &conf_verify_particle}, \
{"foreground", false, &conf_verify_color}, \ {"anchors", false, NULL}, \
{ \ {"font", false, &conf_verify_font}, \
NULL, false, NULL \ {"foreground", false, &conf_verify_color}, \
} {NULL, false, NULL}

View file

@ -1,8 +1,8 @@
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/inotify.h> #include <math.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/inotify.h>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
@ -10,10 +10,10 @@
#define LOG_MODULE "alsa" #define LOG_MODULE "alsa"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../log.h"
#include "../bar/bar.h" #include "../bar/bar.h"
#include "../config-verify.h" #include "../config-verify.h"
#include "../config.h" #include "../config.h"
#include "../log.h"
#include "../plugin.h" #include "../plugin.h"
enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE }; enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE };
@ -23,14 +23,11 @@ struct channel {
enum channel_type type; enum channel_type type;
char *name; char *name;
bool use_db;
long vol_cur; long vol_cur;
long db_cur;
bool muted; bool muted;
}; };
struct private struct private {
{
char *card; char *card;
char *mixer; char *mixer;
char *volume_name; char *volume_name;
@ -45,18 +42,10 @@ struct private
long playback_vol_min; long playback_vol_min;
long playback_vol_max; long playback_vol_max;
bool has_playback_db;
long playback_db_min;
long playback_db_max;
bool has_capture_volume; bool has_capture_volume;
long capture_vol_min; long capture_vol_min;
long capture_vol_max; long capture_vol_max;
long has_capture_db;
long capture_db_min;
long capture_db_max;
const struct channel *volume_chan; const struct channel *volume_chan;
const struct channel *muted_chan; const struct channel *muted_chan;
}; };
@ -71,8 +60,7 @@ static void
destroy(struct module *mod) destroy(struct module *mod)
{ {
struct private *m = mod->private; struct private *m = mod->private;
tll_foreach(m->channels, it) tll_foreach(m->channels, it) {
{
channel_free(&it->item); channel_free(&it->item);
tll_remove(m->channels, it); tll_remove(m->channels, it);
} }
@ -86,10 +74,10 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
static char desc[32]; static char desc[32];
const struct private *m = mod->private; struct private *m = mod->private;
snprintf(desc, sizeof(desc), "alsa(%s)", m->card); snprintf(desc, sizeof(desc), "alsa(%s)", m->card);
return desc; return desc;
} }
@ -106,53 +94,30 @@ content(struct module *mod)
bool muted = muted_chan != NULL ? muted_chan->muted : false; bool muted = muted_chan != NULL ? muted_chan->muted : false;
long vol_min = 0, vol_max = 0, vol_cur = 0; 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 != NULL) {
if (volume_chan->type == CHANNEL_PLAYBACK) { 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_min = m->playback_vol_min;
vol_max = m->playback_vol_max; vol_max = m->playback_vol_max;
} else { } else {
db_min = m->capture_db_min;
db_max = m->capture_db_max;
vol_min = m->capture_vol_min; vol_min = m->capture_vol_min;
vol_max = m->capture_vol_max; vol_max = m->capture_vol_max;
} }
vol_cur = volume_chan->vol_cur; vol_cur = volume_chan->vol_cur;
db_cur = volume_chan->db_cur;
use_db = volume_chan->use_db;
} }
int percent; int percent = vol_max - vol_min > 0
? round(100. * vol_cur / (vol_max - vol_min))
if (use_db) { : 0;
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 = { struct tag_set tags = {
.tags = (struct tag *[]){ .tags = (struct tag *[]){
tag_new_bool(mod, "online", m->online), tag_new_bool(mod, "online", m->online),
tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max), tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max),
tag_new_int_range(mod, "dB", db_cur, db_min, db_max),
tag_new_int_range(mod, "percent", percent, 0, 100), tag_new_int_range(mod, "percent", percent, 0, 100),
tag_new_bool(mod, "muted", muted), tag_new_bool(mod, "muted", muted),
}, },
.count = 5, .count = 4,
}; };
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
@ -167,98 +132,66 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
{ {
struct private *m = mod->private; struct private *m = mod->private;
mtx_lock(&mod->lock);
/* If volume level can be changed (i.e. this isn't just a switch; /* If volume level can be changed (i.e. this isn't just a switch;
* e.g. a digital channel), get current channel levels */ * e.g. a digital channel), get current channel levels */
tll_foreach(m->channels, it) tll_foreach(m->channels, it) {
{
struct channel *chan = &it->item; struct channel *chan = &it->item;
const bool has_volume = chan->type == CHANNEL_PLAYBACK ? m->has_playback_volume : m->has_capture_volume; const bool has_volume = chan->type == CHANNEL_PLAYBACK
const bool has_db = chan->type == CHANNEL_PLAYBACK ? m->has_playback_db : m->has_capture_db; ? m->has_playback_volume : m->has_capture_volume;
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;
if (!has_volume && !has_db) if (!has_volume)
continue; continue;
if (has_db) {
chan->use_db = true;
const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_db_min : m->capture_db_min;
const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_db_max : m->capture_db_max;
assert(min <= max);
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_dB(elem, chan->id, &chan->db_cur)
: snd_mixer_selem_get_capture_dB(elem, chan->id, &chan->db_cur);
if (r < 0) {
LOG_ERR("%s,%s: %s: failed to get current dB", m->card, m->mixer, chan->name);
}
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); assert(min <= max);
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_volume(elem, chan->id, &chan->vol_cur) int r = chan->type == CHANNEL_PLAYBACK
: snd_mixer_selem_get_capture_volume(elem, chan->id, &chan->vol_cur); ? 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) { if (r < 0) {
LOG_ERR("%s,%s: %s: failed to get current volume", m->card, m->mixer, chan->name); LOG_ERR("%s,%s: %s: failed to get current volume",
m->card, m->mixer, chan->name);
} }
if (chan->vol_cur < min) { if (chan->vol_cur < min) {
LOG_WARN("%s,%s: %s: current volume is less than the indicated minimum: " LOG_WARN(
"%ld < %ld", "%s,%s: %s: current volume is less than the indicated minimum: "
m->card, m->mixer, chan->name, chan->vol_cur, min); "%ld < %ld", m->card, m->mixer, chan->name, chan->vol_cur, min);
chan->vol_cur = min; chan->vol_cur = min;
} }
if (chan->vol_cur > max) { if (chan->vol_cur > max) {
LOG_WARN("%s,%s: %s: current volume is greater than the indicated maximum: " LOG_WARN(
"%ld > %ld", "%s,%s: %s: current volume is greater than the indicated maximum: "
m->card, m->mixer, chan->name, chan->vol_cur, max); "%ld > %ld", m->card, m->mixer, chan->name, chan->vol_cur, max);
chan->vol_cur = max; chan->vol_cur = max;
} }
assert(chan->vol_cur >= min); assert(chan->vol_cur >= min);
assert(chan->vol_cur <= max); assert(chan->vol_cur <= max );
LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer, chan->name, chan->vol_cur); LOG_DBG("%s,%s: %s: volume: %ld",
m->card, m->mixer, chan->name, chan->vol_cur);
} }
/* Get channels muted state */ /* Get channels muted state */
tll_foreach(m->channels, it) tll_foreach(m->channels, it) {
{
struct channel *chan = &it->item; struct channel *chan = &it->item;
int unmuted; int unmuted;
int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_switch(elem, chan->id, &unmuted) int r = chan->type == CHANNEL_PLAYBACK
: snd_mixer_selem_get_capture_switch(elem, chan->id, &unmuted); ? snd_mixer_selem_get_playback_switch(elem, chan->id, &unmuted)
: snd_mixer_selem_get_capture_switch(elem, chan->id, &unmuted);
if (r < 0) { if (r < 0) {
LOG_WARN("%s,%s: %s: failed to get muted state", m->card, m->mixer, chan->name); LOG_WARN("%s,%s: %s: failed to get muted state",
m->card, m->mixer, chan->name);
unmuted = 1; unmuted = 1;
} }
@ -266,9 +199,10 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
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, chan->name, !unmuted);
} }
mtx_lock(&mod->lock);
m->online = true; m->online = true;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar); mod->bar->refresh(mod->bar);
} }
@ -294,8 +228,10 @@ run_while_online(struct module *mod)
return ret; return ret;
} }
if (snd_mixer_attach(handle, m->card) != 0 || snd_mixer_selem_register(handle, NULL, NULL) != 0 if (snd_mixer_attach(handle, m->card) != 0 ||
|| snd_mixer_load(handle) != 0) { snd_mixer_selem_register(handle, NULL, NULL) != 0 ||
snd_mixer_load(handle) != 0)
{
LOG_ERR("failed to attach to card"); LOG_ERR("failed to attach to card");
ret = RUN_FAILED_CONNECT; ret = RUN_FAILED_CONNECT;
goto err; goto err;
@ -306,7 +242,7 @@ run_while_online(struct module *mod)
snd_mixer_selem_id_set_index(sid, 0); snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, m->mixer); 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) { if (elem == NULL) {
LOG_ERR("failed to find mixer"); LOG_ERR("failed to find mixer");
goto err; goto err;
@ -315,53 +251,45 @@ run_while_online(struct module *mod)
/* Get playback volume range */ /* Get playback volume range */
m->has_playback_volume = snd_mixer_selem_has_playback_volume(elem) > 0; m->has_playback_volume = snd_mixer_selem_has_playback_volume(elem) > 0;
if (m->has_playback_volume) { if (m->has_playback_volume) {
if (snd_mixer_selem_get_playback_volume_range(elem, &m->playback_vol_min, &m->playback_vol_max) < 0) { if (snd_mixer_selem_get_playback_volume_range(
LOG_ERR("%s,%s: failed to get playback volume range", m->card, m->mixer); 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_min == 0);
assert(m->playback_vol_max == 0); assert(m->playback_vol_max == 0);
} }
if (m->playback_vol_min > m->playback_vol_max) { if (m->playback_vol_min > m->playback_vol_max) {
LOG_WARN("%s,%s: indicated minimum playback volume is greater than the " LOG_WARN(
"maximum: %ld > %ld", "%s,%s: indicated minimum playback volume is greater than the "
m->card, m->mixer, m->playback_vol_min, m->playback_vol_max); "maximum: %ld > %ld",
m->card, m->mixer, m->playback_vol_min, m->playback_vol_max);
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 */ /* Get capture volume range */
m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0; m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0;
if (m->has_capture_volume) { if (m->has_capture_volume) {
if (snd_mixer_selem_get_capture_volume_range(elem, &m->capture_vol_min, &m->capture_vol_max) < 0) { if (snd_mixer_selem_get_capture_volume_range(
LOG_ERR("%s,%s: failed to get capture volume range", m->card, m->mixer); 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_min == 0);
assert(m->capture_vol_max == 0); assert(m->capture_vol_max == 0);
} }
if (m->capture_vol_min > m->capture_vol_max) { if (m->capture_vol_min > m->capture_vol_max) {
LOG_WARN("%s,%s: indicated minimum capture volume is greater than the " LOG_WARN(
"maximum: %ld > %ld", "%s,%s: indicated minimum capture volume is greater than the "
m->card, m->mixer, m->capture_vol_min, m->capture_vol_max); "maximum: %ld > %ld",
m->card, m->mixer, m->capture_vol_min, m->capture_vol_max);
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 */ /* Get available channels */
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) { 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_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1;
@ -371,7 +299,7 @@ run_while_online(struct module *mod)
struct channel chan = { struct channel chan = {
.id = i, .id = i,
.type = is_playback ? CHANNEL_PLAYBACK : CHANNEL_CAPTURE, .type = is_playback ? CHANNEL_PLAYBACK : CHANNEL_CAPTURE,
.name = strdup(snd_mixer_selem_channel_name(i)), .name = strdup(snd_mixer_selem_channel_name( i)),
}; };
tll_push_back(m->channels, chan); tll_push_back(m->channels, chan);
} }
@ -384,13 +312,13 @@ run_while_online(struct module *mod)
char channels_str[1024]; char channels_str[1024];
int channels_idx = 0; int channels_idx = 0;
tll_foreach(m->channels, it) tll_foreach(m->channels, it) {
{
const struct channel *chan = &it->item; const struct channel *chan = &it->item;
channels_idx += snprintf(&channels_str[channels_idx], sizeof(channels_str) - channels_idx, channels_idx += snprintf(
channels_idx == 0 ? "%s (%s)" : ", %s (%s)", chan->name, &channels_str[channels_idx], sizeof(channels_str) - channels_idx,
chan->type == CHANNEL_PLAYBACK ? "🔊" : "🎤"); channels_idx == 0 ? "%s (%s)" : ", %s (%s)",
chan->name, chan->type == CHANNEL_PLAYBACK ? "🔊" : "🎤");
assert(channels_idx <= sizeof(channels_str)); assert(channels_idx <= sizeof(channels_str));
} }
@ -400,8 +328,7 @@ run_while_online(struct module *mod)
bool volume_channel_is_valid = m->volume_name == NULL; bool volume_channel_is_valid = m->volume_name == NULL;
bool muted_channel_is_valid = m->muted_name == NULL; bool muted_channel_is_valid = m->muted_name == NULL;
tll_foreach(m->channels, it) tll_foreach(m->channels, it) {
{
const struct channel *chan = &it->item; const struct channel *chan = &it->item;
if (m->volume_name != NULL && strcmp(chan->name, m->volume_name) == 0) { if (m->volume_name != NULL && strcmp(chan->name, m->volume_name) == 0) {
m->volume_chan = chan; m->volume_chan = chan;
@ -434,14 +361,15 @@ run_while_online(struct module *mod)
update_state(mod, elem); update_state(mod, elem);
LOG_INFO( LOG_INFO(
"%s,%s: %s range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)", m->card, m->mixer, "%s,%s: volume range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)",
m->volume_chan->use_db ? "dB" : "volume", m->card, m->mixer,
(m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_min : m->playback_vol_min) m->volume_chan->type == CHANNEL_PLAYBACK
: (m->volume_chan->use_db ? m->capture_db_min : m->capture_vol_min)), ? m->playback_vol_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->type == CHANNEL_PLAYBACK
: (m->volume_chan->use_db ? m->capture_db_max : m->capture_vol_max)), ? m->playback_vol_max : m->capture_vol_max,
m->volume_chan->use_db ? m->volume_chan->db_cur : m->volume_chan->vol_cur, m->volume_chan->vol_cur,
m->muted_chan->muted ? " (muted)" : "", m->volume_chan->name, m->muted_chan->name); m->muted_chan->muted ? " (muted)" : "",
m->volume_chan->name, m->muted_chan->name);
mod->bar->refresh(mod->bar); mod->bar->refresh(mod->bar);
@ -555,7 +483,8 @@ run(struct module *mod)
bool have_create_event = false; bool have_create_event = false;
while (!have_create_event) { while (!have_create_event) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = ifd, .events = POLLIN}}; struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN},
{.fd = ifd, .events = POLLIN}};
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
if (r < 0) { if (r < 0) {
@ -597,7 +526,7 @@ run(struct module *mod)
break; break;
/* Consume inotify data */ /* Consume inotify data */
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; const struct inotify_event *e = (const struct inotify_event *)ptr;
if (e->mask & IN_CREATE) { if (e->mask & IN_CREATE) {
@ -615,20 +544,23 @@ out:
if (wd >= 0) if (wd >= 0)
inotify_rm_watch(ifd, wd); inotify_rm_watch(ifd, wd);
if (ifd >= 0) if (ifd >= 0)
close(ifd); close (ifd);
return ret; return ret;
} }
static struct module * static struct module *
alsa_new(const char *card, const char *mixer, const char *volume_channel_name, const char *muted_channel_name, alsa_new(const char *card, const char *mixer,
const char *volume_channel_name, const char *muted_channel_name,
struct particle *label) struct particle *label)
{ {
struct private *priv = calloc(1, sizeof(*priv)); struct private *priv = calloc(1, sizeof(*priv));
priv->label = label; priv->label = label;
priv->card = strdup(card); priv->card = strdup(card);
priv->mixer = strdup(mixer); priv->mixer = strdup(mixer);
priv->volume_name = volume_channel_name != NULL ? strdup(volume_channel_name) : NULL; priv->volume_name =
priv->muted_name = muted_channel_name != NULL ? strdup(muted_channel_name) : NULL; 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(); struct module *mod = module_common_new();
mod->private = priv; mod->private = priv;
@ -648,9 +580,12 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *muted = yml_get_value(node, "muted"); const struct yml_node *muted = yml_get_value(node, "muted");
const struct yml_node *content = yml_get_value(node, "content"); const struct yml_node *content = yml_get_value(node, "content");
return alsa_new(yml_value_as_string(card), yml_value_as_string(mixer), return alsa_new(
volume != NULL ? yml_value_as_string(volume) : NULL, yml_value_as_string(card),
muted != NULL ? yml_value_as_string(muted) : NULL, conf_to_particle(content, inherited)); yml_value_as_string(mixer),
volume != NULL ? yml_value_as_string(volume) : NULL,
muted != NULL ? yml_value_as_string(muted) : NULL,
conf_to_particle(content, inherited));
} }
static bool static bool

View file

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

View file

@ -1,49 +1,31 @@
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <poll.h> #include <poll.h>
#include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h>
#include <libudev.h> #include <libudev.h>
#include <tllist.h>
#define LOG_MODULE "battery" #define LOG_MODULE "battery"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h" #include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.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; struct private {
static const long default_poll_interval = 60 * 1000;
static const long one_sec_in_ns = 1000000000;
enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING, STATE_UNKNOWN };
struct current_state {
long ema;
long current;
struct timespec time;
};
struct private
{
struct particle *label; struct particle *label;
long poll_interval; int poll_interval;
int battery_scale;
long smoothing_scale;
char *battery; char *battery;
char *manufacturer; char *manufacturer;
char *model; char *model;
@ -57,64 +39,10 @@ struct private
long energy; long energy;
long power; long power;
long charge; long charge;
struct current_state ema_current; long current;
long time_to_empty; 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 static void
destroy(struct module *mod) destroy(struct module *mod)
{ {
@ -130,10 +58,10 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
static char desc[32]; static char desc[32];
const struct private *m = mod->private; struct private *m = mod->private;
snprintf(desc, sizeof(desc), "bat(%s)", m->battery); snprintf(desc, sizeof(desc), "bat(%s)", m->battery);
return desc; return desc;
} }
@ -145,22 +73,20 @@ content(struct module *mod)
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
assert(m->state == STATE_FULL || m->state == STATE_NOTCHARGING || m->state == STATE_CHARGING assert(m->state == STATE_FULL ||
|| m->state == STATE_DISCHARGING || m->state == STATE_UNKNOWN); m->state == STATE_NOTCHARGING ||
m->state == STATE_CHARGING ||
m->state == STATE_DISCHARGING);
unsigned long hours; unsigned long hours;
unsigned long minutes; unsigned long minutes;
if (m->time_to_empty > 0) { if (m->time_to_empty >= 0) {
minutes = m->time_to_empty / 60; hours = m->time_to_empty / 60;
hours = minutes / 60; minutes = m->time_to_empty % 60;
minutes = minutes % 60; } else if (m->energy_full >= 0 && m->charge && m->power >= 0) {
} else if (m->time_to_full > 0) { unsigned long energy = m->state == STATE_CHARGING
minutes = m->time_to_full / 60; ? m->energy_full - m->energy : m->energy;
hours = minutes / 60;
minutes = minutes % 60;
} else if (m->energy_full >= 0 && m->charge && m->power >= 0) {
unsigned long energy = m->state == STATE_CHARGING ? m->energy_full - m->energy : m->energy;
double hours_as_float; double hours_as_float;
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING) if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
@ -172,14 +98,15 @@ content(struct module *mod)
hours = hours_as_float; hours = hours_as_float;
minutes = (hours_as_float - (double)hours) * 60; minutes = (hours_as_float - (double)hours) * 60;
} else if (m->charge_full >= 0 && m->charge >= 0 && m->ema_current.current >= 0) { } 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; unsigned long charge = m->state == STATE_CHARGING
? m->charge_full - m->charge : m->charge;
double hours_as_float; double hours_as_float;
if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING) if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING)
hours_as_float = 0.0; hours_as_float = 0.0;
else if (m->ema_current.current > 0) else if (m->current > 0)
hours_as_float = (double)charge / m->ema_current.current; hours_as_float = (double)charge / m->current;
else else
hours_as_float = 99.0; hours_as_float = 99.0;
@ -219,18 +146,20 @@ content(struct module *mod)
} }
static const char * 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); lseek(fd, 0, SEEK_SET);
if (bytes < 0) { if (sz < 0) {
LOG_WARN("failed to read from FD=%d", fd); LOG_WARN("failed to read from FD=%d", fd);
return NULL; return NULL;
} }
buf[bytes] = '\0'; buf[sz] = '\0';
for (ssize_t i = bytes - 1; i >= 0 && buf[i] == '\n'; bytes--) for (ssize_t i = sz - 1; i >= 0 && buf[i] == '\n'; sz--)
buf[i] = '\0'; buf[i] = '\0';
return buf; return buf;
@ -239,8 +168,7 @@ readline_from_fd(int fd, size_t sz, char buf[static sz])
static long static long
readint_from_fd(int fd) readint_from_fd(int fd)
{ {
char buf[512]; const char *s = readline_from_fd(fd);
const char *s = readline_from_fd(fd, sizeof(buf), buf);
if (s == NULL) if (s == NULL)
return 0; return 0;
@ -257,15 +185,13 @@ readint_from_fd(int fd)
static bool static bool
initialize(struct private *m) initialize(struct private *m)
{ {
char line_buf[512]; int pw_fd = open("/sys/class/power_supply", O_RDONLY);
int pw_fd = open("/sys/class/power_supply", O_RDONLY | O_CLOEXEC);
if (pw_fd < 0) { if (pw_fd < 0) {
LOG_ERRNO("/sys/class/power_supply"); LOG_ERRNO("/sys/class/power_supply");
return false; 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); close(pw_fd);
if (base_dir_fd < 0) { 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) { 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; m->manufacturer = NULL;
} else { } else {
m->manufacturer = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf)); m->manufacturer = strdup(readline_from_fd(fd));
close(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) { 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; m->model = NULL;
} else { } else {
m->model = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf)); m->model = strdup(readline_from_fd(fd));
close(fd); close(fd);
} }
} }
if (faccessat(base_dir_fd, "energy_full_design", 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) { 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) { if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery); LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery);
goto err; 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) { if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery); LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery);
goto err; goto err;
@ -322,27 +251,28 @@ initialize(struct private *m)
m->energy_full = m->energy_full_design = -1; m->energy_full = m->energy_full_design = -1;
} }
if (faccessat(base_dir_fd, "charge_full_design", 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) { 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) { if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery); LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery);
goto err; goto err;
} }
m->charge_full_design = readint_from_fd(fd) / m->battery_scale; m->charge_full_design = readint_from_fd(fd);
close(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) { if (fd == -1) {
LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery); LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery);
goto err; goto err;
} }
m->charge_full = readint_from_fd(fd) / m->battery_scale; m->charge_full = readint_from_fd(fd);
close(fd); close(fd);
} }
} else { } else {
@ -362,13 +292,13 @@ update_status(struct module *mod)
{ {
struct private *m = mod->private; 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) { if (pw_fd < 0) {
LOG_ERRNO("/sys/class/power_supply"); LOG_ERRNO("/sys/class/power_supply");
return false; 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); close(pw_fd);
if (base_dir_fd < 0) { if (base_dir_fd < 0) {
@ -376,14 +306,14 @@ update_status(struct module *mod)
return false; 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) { if (status_fd < 0) {
LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery); LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery);
close(base_dir_fd); close(base_dir_fd);
return false; 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) { if (capacity_fd < 0) {
LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery); LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery);
close(status_fd); close(status_fd);
@ -391,12 +321,11 @@ update_status(struct module *mod)
return false; return false;
} }
int energy_fd = openat(base_dir_fd, "energy_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 | O_CLOEXEC); int power_fd = openat(base_dir_fd, "power_now", O_RDONLY);
int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY | O_CLOEXEC); int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY);
int current_fd = openat(base_dir_fd, "current_now", O_RDONLY | O_CLOEXEC); 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 | O_CLOEXEC); int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY);
int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY | O_CLOEXEC);
long capacity = readint_from_fd(capacity_fd); long capacity = readint_from_fd(capacity_fd);
long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1; 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 charge = charge_fd >= 0 ? readint_from_fd(charge_fd) : -1;
long current = current_fd >= 0 ? readint_from_fd(current_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_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) { const char *status = readline_from_fd(status_fd);
charge /= m->battery_scale;
}
char buf[512];
const char *status = readline_from_fd(status_fd, sizeof(buf), buf);
if (status_fd >= 0) if (status_fd >= 0)
close(status_fd); close(status_fd);
@ -427,8 +350,6 @@ update_status(struct module *mod)
close(current_fd); close(current_fd);
if (time_to_empty_fd >= 0) if (time_to_empty_fd >= 0)
close(time_to_empty_fd); close(time_to_empty_fd);
if (time_to_full_fd >= 0)
close(time_to_full_fd);
if (base_dir_fd >= 0) if (base_dir_fd >= 0)
close(base_dir_fd); close(base_dir_fd);
@ -436,7 +357,7 @@ update_status(struct module *mod)
if (status == NULL) { if (status == NULL) {
LOG_WARN("failed to read battery state"); LOG_WARN("failed to read battery state");
state = STATE_UNKNOWN; state = STATE_DISCHARGING;
} else if (strcmp(status, "Full") == 0) } else if (strcmp(status, "Full") == 0)
state = STATE_FULL; state = STATE_FULL;
else if (strcmp(status, "Not charging") == 0) else if (strcmp(status, "Not charging") == 0)
@ -446,32 +367,24 @@ update_status(struct module *mod)
else if (strcmp(status, "Discharging") == 0) else if (strcmp(status, "Discharging") == 0)
state = STATE_DISCHARGING; state = STATE_DISCHARGING;
else if (strcmp(status, "Unknown") == 0) else if (strcmp(status, "Unknown") == 0)
state = STATE_UNKNOWN; state = STATE_DISCHARGING;
else { else {
LOG_ERR("unrecognized battery state: %s", status); 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, " LOG_DBG("capacity: %ld, energy: %ld, power: %ld, charge=%ld, current=%ld, "
"time-to-empty: %ld, time-to-full: %ld", "time-to-empty: %ld", capacity, energy, power, charge, current,
capacity, energy, power, charge, current, time_to_empty, time_to_full); time_to_empty);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
if (m->state != state) {
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
}
m->state = state; m->state = state;
m->capacity = capacity; m->capacity = capacity;
m->energy = energy; m->energy = energy;
m->power = power; m->power = power;
m->charge = charge; m->charge = charge;
if (current != -1) { m->current = current;
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
ema_linear(&m->ema_current, (struct current_state){current, current, t}, m->smoothing_scale);
}
m->time_to_empty = time_to_empty; m->time_to_empty = time_to_empty;
m->time_to_full = time_to_full;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return true; return true;
} }
@ -485,10 +398,13 @@ run(struct module *mod)
if (!initialize(m)) if (!initialize(m))
return -1; return -1;
LOG_INFO("%s: %s %s (at %.1f%% of original capacity)", m->battery, m->manufacturer, m->model, LOG_INFO("%s: %s %s (at %.1f%% of original capacity)",
(m->energy_full > 0 ? 100.0 * m->energy_full / m->energy_full_design m->battery, m->manufacturer, m->model,
: m->charge_full > 0 ? 100.0 * m->charge_full / m->charge_full_design (m->energy_full > 0
: 0.0)); ? 100.0 * m->energy_full / m->energy_full_design
: m->charge_full > 0
? 100.0 * m->charge_full / m->charge_full_design
: 0.0));
int ret = 1; int ret = 1;
@ -506,82 +422,31 @@ run(struct module *mod)
bar->refresh(bar); bar->refresh(bar);
int timeout_left_ms = m->poll_interval;
while (true) { while (true) {
struct pollfd fds[] = { struct pollfd fds[] = {
{.fd = mod->abort_fd, .events = POLLIN}, {.fd = mod->abort_fd, .events = POLLIN},
{.fd = udev_monitor_get_fd(mon), .events = POLLIN}, {.fd = udev_monitor_get_fd(mon), .events = POLLIN},
}; };
poll(fds, 2, m->poll_interval > 0 ? m->poll_interval * 1000 : -1);
int timeout = m->poll_interval > 0 ? timeout_left_ms : -1;
struct timespec time_before_poll;
if (clock_gettime(CLOCK_BOOTTIME, &time_before_poll) < 0) {
LOG_ERRNO("failed to get current time");
break;
}
const int poll_ret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout);
if (poll_ret < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if (fds[0].revents & POLLIN) { if (fds[0].revents & POLLIN) {
ret = 0; ret = 0;
break; break;
} }
bool udev_for_us = false;
if (fds[1].revents & POLLIN) { if (fds[1].revents & POLLIN) {
struct udev_device *dev = udev_monitor_receive_device(mon); struct udev_device *dev = udev_monitor_receive_device(mon);
if (dev != NULL) { const char *sysname = udev_device_get_sysname(dev);
const char *sysname = udev_device_get_sysname(dev);
udev_for_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
if (!udev_for_us) { bool is_us = sysname != NULL && strcmp(sysname, m->battery) == 0;
LOG_DBG("udev notification not for us (%s != %s)", m->battery, udev_device_unref(dev);
sysname != sysname ? sysname : "NULL");
} else
LOG_DBG("triggering update due to udev notification");
udev_device_unref(dev); if (!is_us)
} continue;
} }
if (udev_for_us || poll_ret == 0) { if (update_status(mod))
if (update_status(mod)) bar->refresh(bar);
bar->refresh(bar);
}
if (poll_ret == 0 || udev_for_us) {
LOG_DBG("resetting timeout-left to %ldms", m->poll_interval);
timeout_left_ms = m->poll_interval;
} else {
struct timespec time_after_poll;
if (clock_gettime(CLOCK_BOOTTIME, &time_after_poll) < 0) {
LOG_ERRNO("failed to get current time");
break;
}
struct timespec timeout_consumed;
timespec_sub(&time_after_poll, &time_before_poll, &timeout_consumed);
const int timeout_consumed_ms = timeout_consumed.tv_sec * 1000 + timeout_consumed.tv_nsec / 1000000;
LOG_DBG("timeout-left before: %dms, consumed: %dms, updated: %dms", timeout_left_ms, timeout_consumed_ms,
max(timeout_left_ms - timeout_consumed_ms, 0));
timeout_left_ms -= timeout_consumed_ms;
if (timeout_left_ms < 0)
timeout_left_ms = 0;
}
} }
out: out:
@ -593,17 +458,13 @@ out:
} }
static struct module * static struct module *
battery_new(const char *battery, struct particle *label, long poll_interval_msecs, int battery_scale, battery_new(const char *battery, struct particle *label, int poll_interval_secs)
long smoothing_secs)
{ {
struct private *m = calloc(1, sizeof(*m)); struct private *m = calloc(1, sizeof(*m));
m->label = label; m->label = label;
m->poll_interval = poll_interval_msecs; m->poll_interval = poll_interval_secs;
m->battery_scale = battery_scale;
m->smoothing_scale = smoothing_secs * one_sec_in_ns;
m->battery = strdup(battery); m->battery = strdup(battery);
m->state = STATE_UNKNOWN; m->state = STATE_DISCHARGING;
m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}};
struct module *mod = module_common_new(); struct module *mod = module_common_new();
mod->private = m; 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 *c = yml_get_value(node, "content");
const struct yml_node *name = yml_get_value(node, "name"); 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 *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), return battery_new(
(poll_interval != NULL ? yml_value_as_int(poll_interval) : default_poll_interval), yml_value_as_string(name),
(battery_scale != NULL ? yml_value_as_int(battery_scale) : 1), conf_to_particle(c, inherited),
(smoothing_secs != NULL ? yml_value_as_int(smoothing_secs) : 100)); poll_interval != NULL ? yml_value_as_int(poll_interval) : 60);
}
static bool
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
{
if (!conf_verify_unsigned(chain, node))
return false;
const long value = yml_value_as_int(node);
if (value != 0 && value < min_poll_interval) {
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
return false;
}
return true;
} }
static bool static bool
@ -650,9 +493,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"name", true, &conf_verify_string}, {"name", true, &conf_verify_string},
{"poll-interval", false, &conf_verify_poll_interval}, {"poll-interval", false, &conf_verify_int},
{"battery-scale", false, &conf_verify_unsigned},
{"smoothing-secs", false, &conf_verify_unsigned},
MODULE_COMMON_ATTRS, MODULE_COMMON_ATTRS,
}; };

View file

@ -1,22 +1,20 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <assert.h>
#include <poll.h> #include <poll.h>
#include <sys/time.h> #include <sys/time.h>
#define LOG_MODULE "clock" #define LOG_MODULE "clock"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h" #include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h" #include "../plugin.h"
struct private struct private {
{
struct particle *label; struct particle *label;
enum { enum {
UPDATE_GRANULARITY_SECONDS, UPDATE_GRANULARITY_SECONDS,
@ -24,7 +22,6 @@ struct private
} update_granularity; } update_granularity;
char *date_format; char *date_format;
char *time_format; char *time_format;
bool utc;
}; };
static void static void
@ -39,7 +36,7 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
return "clock"; return "clock";
} }
@ -49,7 +46,7 @@ content(struct module *mod)
{ {
const struct private *m = mod->private; const struct private *m = mod->private;
time_t t = time(NULL); time_t t = time(NULL);
struct tm *tm = m->utc ? gmtime(&t) : localtime(&t); struct tm *tm = localtime(&t);
char date_str[1024]; char date_str[1024];
strftime(date_str, sizeof(date_str), m->date_format, tm); 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); strftime(time_str, sizeof(time_str), m->time_format, tm);
struct tag_set tags = { 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, .count = 2,
}; };
@ -68,6 +66,7 @@ content(struct module *mod)
return exposable; return exposable;
} }
#include <pthread.h>
static int static int
run(struct module *mod) run(struct module *mod)
{ {
@ -75,8 +74,6 @@ run(struct module *mod)
const struct bar *bar = mod->bar; const struct bar *bar = mod->bar;
bar->refresh(bar); bar->refresh(bar);
int ret = 1;
while (true) { while (true) {
struct timespec _now; struct timespec _now;
clock_gettime(CLOCK_REALTIME, &_now); clock_gettime(CLOCK_REALTIME, &_now);
@ -90,12 +87,15 @@ run(struct module *mod)
switch (m->update_granularity) { switch (m->update_granularity) {
case UPDATE_GRANULARITY_SECONDS: { 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; struct timeval _timeout;
timersub(&next_second, &now, &_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; timeout_ms = _timeout.tv_usec / 1000;
break; break;
} }
@ -115,44 +115,44 @@ run(struct module *mod)
/* Add 1ms to account for rounding errors */ /* Add 1ms to account for rounding errors */
timeout_ms++; 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}}; struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
if (poll(fds, 1, timeout_ms) < 0) { poll(fds, 1, timeout_ms);
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll"); if (fds[0].revents & POLLIN)
break; break;
}
if (fds[0].revents & POLLIN) {
ret = 0;
break;
}
bar->refresh(bar); bar->refresh(bar);
} }
return ret; return 0;
} }
static struct module * 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)); struct private *m = calloc(1, sizeof(*m));
m->label = label; m->label = label;
m->date_format = strdup(date_format); m->date_format = strdup(date_format);
m->time_format = strdup(time_format); m->time_format = strdup(time_format);
m->utc = utc;
static const char *const seconds_formatters[] = { static const char *const seconds_formatters[] = {
"%c", "%s", "%S", "%T", "%r", "%X", "%c",
"%s",
"%S",
"%T",
"%r",
"%X",
}; };
m->update_granularity = UPDATE_GRANULARITY_MINUTES; 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) { if (strstr(time_format, seconds_formatters[i]) != NULL) {
m->update_granularity = UPDATE_GRANULARITY_SECONDS; m->update_granularity = UPDATE_GRANULARITY_SECONDS;
break; break;
@ -160,7 +160,8 @@ clock_new(struct particle *label, const char *date_format, const char *time_form
} }
LOG_DBG("using %s update granularity", 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(); struct module *mod = module_common_new();
mod->private = m; 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 *c = yml_get_value(node, "content");
const struct yml_node *date_format = yml_get_value(node, "date-format"); 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 *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", return clock_new(
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M", conf_to_particle(c, inherited),
utc != NULL ? yml_value_as_bool(utc) : false); date_format != NULL ? yml_value_as_string(date_format) : "%x",
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M");
} }
static bool static bool
@ -190,7 +191,6 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"date-format", false, &conf_verify_string}, {"date-format", false, &conf_verify_string},
{"time-format", false, &conf_verify_string}, {"time-format", false, &conf_verify_string},
{"utc", false, &conf_verify_bool},
MODULE_COMMON_ATTRS, 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,7 +1,7 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <errno.h>
#include <poll.h> #include <poll.h>
@ -11,8 +11,8 @@
#define LOG_MODULE "foreign-toplevel" #define LOG_MODULE "foreign-toplevel"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../log.h" #include "../log.h"
#include "../particles/dynlist.h"
#include "../plugin.h" #include "../plugin.h"
#include "../particles/dynlist.h"
#include "wlr-foreign-toplevel-management-unstable-v1.h" #include "wlr-foreign-toplevel-management-unstable-v1.h"
#include "xdg-output-unstable-v1.h" #include "xdg-output-unstable-v1.h"
@ -46,8 +46,7 @@ struct toplevel {
tll(const struct output *) outputs; tll(const struct output *) outputs;
}; };
struct private struct private {
{
struct particle *template; struct particle *template;
uint32_t manager_wl_name; uint32_t manager_wl_name;
struct zwlr_foreign_toplevel_manager_v1 *manager; struct zwlr_foreign_toplevel_manager_v1 *manager;
@ -93,7 +92,7 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
return "toplevel"; return "toplevel";
} }
@ -111,8 +110,7 @@ content(struct module *mod)
const char *current_output = mod->bar->output_name(mod->bar); const char *current_output = mod->bar->output_name(mod->bar);
tll_foreach(m->toplevels, it) tll_foreach(m->toplevels, it) {
{
const struct toplevel *top = &it->item; const struct toplevel *top = &it->item;
bool show = false; bool show = false;
@ -120,10 +118,11 @@ content(struct module *mod)
if (m->all_monitors) if (m->all_monitors)
show = true; show = true;
else if (current_output != NULL) { else if (current_output != NULL) {
tll_foreach(top->outputs, it2) tll_foreach(top->outputs, it2) {
{
const struct output *output = it2->item; const struct output *output = it2->item;
if (output->name != NULL && strcmp(output->name, current_output) == 0) { if (output->name != NULL &&
strcmp(output->name, current_output) == 0)
{
show = true; show = true;
break; break;
} }
@ -159,18 +158,22 @@ verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
if (version >= wanted) if (version >= wanted)
return true; return true;
LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version); LOG_ERR("%s: need interface version %u, but compositor only implements %u",
iface, wanted, version);
return false; return false;
} }
static void static void
xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) xdg_output_handle_logical_position(void *data,
struct zxdg_output_v1 *xdg_output,
int32_t x, int32_t y)
{ {
} }
static void static void
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
int32_t width, int32_t height)
{ {
} }
@ -180,7 +183,8 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
} }
static void static void
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
const char *name)
{ {
struct output *output = data; struct output *output = data;
struct module *mod = output->mod; struct module *mod = output->mod;
@ -194,7 +198,8 @@ xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char
} }
static void static void
xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output,
const char *description)
{ {
} }
@ -233,7 +238,8 @@ app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *a
} }
static void static void
output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output) output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct wl_output *wl_output)
{ {
struct toplevel *top = data; struct toplevel *top = data;
struct module *mod = top->mod; struct module *mod = top->mod;
@ -242,8 +248,7 @@ output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
const struct output *output = NULL; const struct output *output = NULL;
tll_foreach(m->outputs, it) tll_foreach(m->outputs, it) {
{
if (it->item.wl_output == wl_output) { if (it->item.wl_output == wl_output) {
output = &it->item; output = &it->item;
break; break;
@ -255,8 +260,7 @@ output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
goto out; goto out;
} }
tll_foreach(top->outputs, it) tll_foreach(top->outputs, it) {
{
if (it->item == output) { if (it->item == output) {
LOG_ERR("output-enter event on output we're already on"); LOG_ERR("output-enter event on output we're already on");
goto out; goto out;
@ -271,7 +275,8 @@ out:
} }
static void static void
output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output) output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct wl_output *wl_output)
{ {
struct toplevel *top = data; struct toplevel *top = data;
struct module *mod = top->mod; struct module *mod = top->mod;
@ -280,8 +285,7 @@ output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
const struct output *output = NULL; const struct output *output = NULL;
tll_foreach(m->outputs, it) tll_foreach(m->outputs, it) {
{
if (it->item.wl_output == wl_output) { if (it->item.wl_output == wl_output) {
output = &it->item; output = &it->item;
break; break;
@ -294,10 +298,10 @@ output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
} }
bool output_removed = false; bool output_removed = false;
tll_foreach(top->outputs, it) tll_foreach(top->outputs, it) {
{
if (it->item == output) { if (it->item == output) {
LOG_DBG("unmapped: %s:%s from %s", top->app_id, top->title, output->name); LOG_DBG("unmapped: %s:%s from %s",
top->app_id, top->title, output->name);
tll_remove(top->outputs, it); tll_remove(top->outputs, it);
output_removed = true; output_removed = true;
break; break;
@ -314,7 +318,8 @@ out:
} }
static void static void
state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *states) state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle,
struct wl_array *states)
{ {
struct toplevel *top = data; struct toplevel *top = data;
@ -324,21 +329,12 @@ state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_arra
bool fullscreen = false; bool fullscreen = false;
enum zwlr_foreign_toplevel_handle_v1_state *state; enum zwlr_foreign_toplevel_handle_v1_state *state;
wl_array_for_each(state, states) wl_array_for_each(state, states) {
{
switch (*state) { switch (*state) {
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: maximized = true; break;
maximized = true; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: minimized = true; break;
break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: activated = true; break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: fullscreen = true; break;
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;
} }
} }
@ -368,8 +364,7 @@ closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
struct private *m = mod->private; struct private *m = mod->private;
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_foreach(m->toplevels, it) tll_foreach(m->toplevels, it) {
{
if (it->item.handle == handle) { if (it->item.handle == handle) {
toplevel_free(top); toplevel_free(top);
tll_remove(m->toplevels, it); tll_remove(m->toplevels, it);
@ -377,13 +372,12 @@ closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
} }
} }
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
const struct bar *bar = mod->bar;
bar->refresh(bar);
} }
static void static void
parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent) parent(void *data,
struct zwlr_foreign_toplevel_handle_v1 *handle,
struct zwlr_foreign_toplevel_handle_v1 *parent)
{ {
} }
@ -399,7 +393,9 @@ static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_listener =
}; };
static void static void
toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *handle) toplevel(void *data,
struct zwlr_foreign_toplevel_manager_v1 *manager,
struct zwlr_foreign_toplevel_handle_v1 *handle)
{ {
struct module *mod = data; struct module *mod = data;
struct private *m = mod->private; struct private *m = mod->private;
@ -413,13 +409,15 @@ toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zw
{ {
tll_push_back(m->toplevels, toplevel); tll_push_back(m->toplevels, toplevel);
zwlr_foreign_toplevel_handle_v1_add_listener(handle, &toplevel_listener, &tll_back(m->toplevels)); zwlr_foreign_toplevel_handle_v1_add_listener(
handle, &toplevel_listener, &tll_back(m->toplevels));
} }
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
} }
static void static void
finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager) finished(void *data,
struct zwlr_foreign_toplevel_manager_v1 *manager)
{ {
struct module *mod = data; struct module *mod = data;
struct private *m = mod->private; struct private *m = mod->private;
@ -444,12 +442,15 @@ output_xdg_output(struct output *output)
if (output->xdg_output != NULL) if (output->xdg_output != NULL)
return; return;
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(m->xdg_output_manager, output->wl_output); output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); m->xdg_output_manager, output->wl_output);
zxdg_output_v1_add_listener(
output->xdg_output, &xdg_output_listener, output);
} }
static void static void
handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{ {
struct module *mod = data; struct module *mod = data;
struct private *m = mod->private; struct private *m = mod->private;
@ -469,7 +470,8 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
struct output output = { struct output output = {
.mod = mod, .mod = mod,
.wl_name = name, .wl_name = name,
.wl_output = wl_registry_bind(registry, name, &wl_output_interface, required), .wl_output = wl_registry_bind(
registry, name, &wl_output_interface, required),
}; };
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
@ -483,10 +485,12 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
if (!verify_iface_version(interface, version, required)) if (!verify_iface_version(interface, version, required))
return; return;
m->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, required); m->xdg_output_manager = wl_registry_bind(
registry, name, &zxdg_output_manager_v1_interface, required);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_foreach(m->outputs, it) output_xdg_output(&it->item); tll_foreach(m->outputs, it)
output_xdg_output(&it->item);
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
} }
} }
@ -499,19 +503,16 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_foreach(m->outputs, it) tll_foreach(m->outputs, it) {
{
const struct output *output = &it->item; const struct output *output = &it->item;
if (output->wl_name == name) { if (output->wl_name == name) {
/* Loop all toplevels */ /* Loop all toplevels */
tll_foreach(m->toplevels, it2) tll_foreach(m->toplevels, it2) {
{
/* And remove this output from their list of tracked /* And remove this output from their list of tracked
* outputs */ * outputs */
tll_foreach(it2->item.outputs, it3) tll_foreach(it2->item.outputs, it3) {
{
if (it3->item == output) { if (it3->item == output) {
tll_remove(it2->item.outputs, it3); tll_remove(it2->item.outputs, it3);
break; break;
@ -547,8 +548,9 @@ run(struct module *mod)
goto out; goto out;
} }
if ((registry = wl_display_get_registry(display)) == NULL if ((registry = wl_display_get_registry(display)) == NULL ||
|| wl_registry_add_listener(registry, &registry_listener, mod) != 0) { wl_registry_add_listener(registry, &registry_listener, mod) != 0)
{
LOG_ERR("failed to get Wayland registry"); LOG_ERR("failed to get Wayland registry");
goto out; goto out;
} }
@ -556,14 +558,18 @@ run(struct module *mod)
wl_display_roundtrip(display); wl_display_roundtrip(display);
if (m->manager_wl_name == 0) { if (m->manager_wl_name == 0) {
LOG_ERR("compositor does not implement the foreign-toplevel-manager interface"); LOG_ERR(
"compositor does not implement the foreign-toplevel-manager interface");
goto out; goto out;
} }
m->manager = wl_registry_bind(registry, m->manager_wl_name, &zwlr_foreign_toplevel_manager_v1_interface, m->manager = wl_registry_bind(
required_manager_interface_version); 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); zwlr_foreign_toplevel_manager_v1_add_listener(
m->manager, &manager_listener, mod);
while (true) { while (true) {
wl_display_flush(display); wl_display_flush(display);
@ -597,14 +603,12 @@ run(struct module *mod)
} }
out: out:
tll_foreach(m->toplevels, it) tll_foreach(m->toplevels, it) {
{
toplevel_free(&it->item); toplevel_free(&it->item);
tll_remove(m->toplevels, it); tll_remove(m->toplevels, it);
} }
tll_foreach(m->outputs, it) tll_foreach(m->outputs, it) {
{
output_free(&it->item); output_free(&it->item);
tll_remove(m->outputs, it); tll_remove(m->outputs, it);
} }
@ -642,7 +646,9 @@ 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 *c = yml_get_value(node, "content");
const struct yml_node *all_monitors = yml_get_value(node, "all-monitors"); 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); return ftop_new(
conf_to_particle(c, inherited),
all_monitors != NULL ? yml_value_as_bool(all_monitors) : false);
} }
static bool static bool

View file

@ -1,15 +1,15 @@
#include "i3-common.h" #include "i3-common.h"
#include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#include <poll.h> #include <poll.h>
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_aux.h> #include <xcb/xcb_aux.h>
#endif #endif
#include <json-c/json_tokener.h> #include <json-c/json_tokener.h>
@ -19,7 +19,7 @@
#include "../log.h" #include "../log.h"
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
#include "../xcb.h" #include "../xcb.h"
#endif #endif
#include "i3-ipc.h" #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"); xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH");
assert(atom != XCB_ATOM_NONE); assert(atom != XCB_ATOM_NONE);
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(conn, false, screen->root, atom, xcb_get_property_cookie_t cookie
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path)); = 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_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; bool ret = false;
if (err != NULL) { if (err != NULL) {
@ -99,7 +102,11 @@ bool
i3_send_pkg(int sock, int cmd, char *data) i3_send_pkg(int sock, int cmd, char *data)
{ {
const size_t size = data != NULL ? strlen(data) : 0; 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)) if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr))
return false; return false;
@ -113,7 +120,8 @@ i3_send_pkg(int sock, int cmd, char *data)
} }
bool 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 /* Initial reply typically requires a couple of KB. But we often
* need more later. For example, switching workspaces can result * 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; bool err = false;
while (!err) { 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); int res = poll(fds, 2, -1);
if (res <= 0) { 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 */ /* Grow receive buffer, if necessary */
if (buf_idx == reply_buf_size) { 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); char *new_buf = realloc(buf, reply_buf_size * 2);
if (new_buf == NULL) { 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; err = true;
break; 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)) { while (!err && buf_idx >= sizeof(i3_ipc_header_t)) {
const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf; const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf;
if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) { 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), LOG_ERR(
I3_IPC_MAGIC, (int)sizeof(hdr->magic), hdr->magic); "i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"",
(int)sizeof(hdr->magic), I3_IPC_MAGIC,
(int)sizeof(hdr->magic), hdr->magic);
err = true; err = true;
break; 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]; char json_str[hdr->size + 1];
memcpy(json_str, &buf[sizeof(*hdr)], hdr->size); memcpy(json_str, &buf[sizeof(*hdr)], hdr->size);
json_str[hdr->size] = '\0'; json_str[hdr->size] = '\0';
// printf("raw: %s\n", json_str); //printf("raw: %s\n", json_str);
LOG_DBG("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); struct json_object *json = json_tokener_parse(json_str);
if (json == NULL) { if (json == NULL) {
LOG_ERR("failed to parse json"); 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; break;
#endif #endif
/* Sway extensions */ /* Sway extensions */
case 100: /* IPC_GET_INPUTS */ case 100: /* IPC_GET_INPUTS */
pkt_handler = cbs->reply_inputs; pkt_handler = cbs->reply_inputs;
break; break;
/* /*
* Events * Events
*/ */
case I3_IPC_EVENT_WORKSPACE: case I3_IPC_EVENT_WORKSPACE:
pkt_handler = cbs->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; pkt_handler = cbs->event_tick;
break; break;
/* Sway extensions */ /* Sway extensions */
#define SWAY_IPC_EVENT_INPUT ((1u << 31) | 21) #define SWAY_IPC_EVENT_INPUT ((1u << 31) | 21)
case SWAY_IPC_EVENT_INPUT: case SWAY_IPC_EVENT_INPUT:
pkt_handler = cbs->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) if (pkt_handler != NULL)
err = !pkt_handler(sock, hdr->type, json, data); err = !pkt_handler(hdr->type, json, data);
else else
LOG_DBG("no handler for reply/event %d; ignoring", hdr->type); LOG_DBG("no handler for reply/event %d; ignoring", hdr->type);

View file

@ -2,8 +2,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <json-c/json_util.h> #include <json-c/json_util.h>
@ -11,7 +11,7 @@
bool i3_get_socket_address(struct sockaddr_un *addr); bool i3_get_socket_address(struct sockaddr_un *addr);
bool i3_send_pkg(int sock, int cmd, char *data); 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 { struct i3_ipc_callbacks {
void (*burst_done)(void *data); void (*burst_done)(void *data);
@ -43,4 +43,6 @@ struct i3_ipc_callbacks {
i3_ipc_callback_t event_input; 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 <stdlib.h>
#include <string.h> #include <string.h>
#include <threads.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#include <threads.h>
#include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h>
#include <tllist.h> #include <tllist.h>
#define LOG_MODULE "i3" #define LOG_MODULE "i3"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h" #include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../particles/dynlist.h" #include "../particles/dynlist.h"
#include "../plugin.h" #include "../plugin.h"
#include "i3-common.h"
#include "i3-ipc.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 { struct ws_content {
char *name; char *name;
@ -29,7 +29,6 @@ struct ws_content {
}; };
struct workspace { struct workspace {
int id;
char *name; char *name;
int name_as_int; /* -1 if name is not a decimal number */ int name_as_int; /* -1 if name is not a decimal number */
bool persistent; bool persistent;
@ -38,7 +37,6 @@ struct workspace {
bool visible; bool visible;
bool focused; bool focused;
bool urgent; bool urgent;
bool empty;
struct { struct {
unsigned id; unsigned id;
@ -48,8 +46,7 @@ struct workspace {
} window; } window;
}; };
struct private struct private {
{
int left_spacing; int left_spacing;
int right_spacing; int right_spacing;
@ -62,7 +59,6 @@ struct private
size_t count; size_t count;
} ws_content; } ws_content;
bool strip_workspace_numbers;
enum sort_mode sort_mode; enum sort_mode sort_mode;
tll(struct workspace) workspaces; tll(struct workspace) workspaces;
@ -74,22 +70,6 @@ static int
workspace_name_as_int(const char *name) workspace_name_as_int(const char *name)
{ {
int name_as_int = 0; 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++) { for (const char *p = name; *p != '\0'; p++) {
if (!(*p >= '0' && *p <= '9')) if (!(*p >= '0' && *p <= '9'))
return -1; return -1;
@ -105,18 +85,14 @@ static bool
workspace_from_json(const struct json_object *json, struct workspace *ws) workspace_from_json(const struct json_object *json, struct workspace *ws)
{ {
/* Always present */ /* Always present */
struct json_object *id, *name, *output; struct json_object *name, *output;
if (!json_object_object_get_ex(json, "id", &id) || !json_object_object_get_ex(json, "name", &name) if (!json_object_object_get_ex(json, "name", &name) ||
|| !json_object_object_get_ex(json, "output", &output)) { !json_object_object_get_ex(json, "output", &output))
LOG_ERR("workspace reply/event without 'name' and/or 'output' " {
"properties"); LOG_ERR("workspace reply/event without 'name' and/or 'output' property");
return false; return false;
} }
/* Sway only */
struct json_object *focus = NULL;
json_object_object_get_ex(json, "focus", &focus);
/* Optional */ /* Optional */
struct json_object *visible = NULL, *focused = NULL, *urgent = NULL; struct json_object *visible = NULL, *focused = NULL, *urgent = NULL;
json_object_object_get_ex(json, "visible", &visible); json_object_object_get_ex(json, "visible", &visible);
@ -125,52 +101,33 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
const char *name_as_string = json_object_get_string(name); const char *name_as_string = json_object_get_string(name);
const size_t node_count = focus != NULL ? json_object_array_length(focus) : 0; *ws = (struct workspace) {
const bool is_empty = node_count == 0;
int name_as_int = workspace_name_as_int(name_as_string);
*ws = (struct workspace){
.id = json_object_get_int(id),
.name = strdup(name_as_string), .name = strdup(name_as_string),
.name_as_int = name_as_int, .name_as_int = workspace_name_as_int(name_as_string),
.persistent = false, .persistent = false,
.output = strdup(json_object_get_string(output)), .output = strdup(json_object_get_string(output)),
.visible = json_object_get_boolean(visible), .visible = json_object_get_boolean(visible),
.focused = json_object_get_boolean(focused), .focused = json_object_get_boolean(focused),
.urgent = json_object_get_boolean(urgent), .urgent = json_object_get_boolean(urgent),
.empty = is_empty && json_object_get_boolean(focused),
.window = {.title = NULL, .pid = -1}, .window = {.title = NULL, .pid = -1},
}; };
return true; 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 static void
workspace_free(struct workspace *ws) workspace_free(struct workspace *ws)
{ {
workspace_free_persistent(ws); free(ws->name); ws->name = NULL;
free(ws->name); free(ws->output); ws->output = NULL;
ws->name = NULL; free(ws->window.title); ws->window.title = NULL;
free(ws->window.application); ws->window.application = NULL;
} }
static void static void
workspaces_free(struct private *m, bool free_persistent) workspaces_free(struct private *m, bool free_persistent)
{ {
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{
if (free_persistent || !it->item.persistent) { if (free_persistent || !it->item.persistent) {
workspace_free(&it->item); workspace_free(&it->item);
tll_remove(m->workspaces, it); tll_remove(m->workspaces, it);
@ -178,6 +135,7 @@ workspaces_free(struct private *m, bool free_persistent)
} }
} }
static void static void
workspace_add(struct private *m, struct workspace ws) workspace_add(struct private *m, struct workspace ws)
{ {
@ -186,26 +144,9 @@ workspace_add(struct private *m, struct workspace ws)
tll_push_back(m->workspaces, ws); tll_push_back(m->workspaces, ws);
return; 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: case SORT_ASCENDING:
if (ws.name_as_int >= 0) { if (ws.name_as_int >= 0) {
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{
if (it->item.name_as_int < 0) if (it->item.name_as_int < 0)
continue; continue;
if (it->item.name_as_int > ws.name_as_int) { if (it->item.name_as_int > ws.name_as_int) {
@ -214,9 +155,10 @@ workspace_add(struct private *m, struct workspace ws)
} }
} }
} else { } else {
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{ if (strcoll(it->item.name, ws.name) > 0 ||
if (strcoll(it->item.name, ws.name) > 0 || it->item.name_as_int >= 0) { it->item.name_as_int >= 0)
{
tll_insert_before(m->workspaces, it, ws); tll_insert_before(m->workspaces, it, ws);
return; return;
} }
@ -227,16 +169,14 @@ workspace_add(struct private *m, struct workspace ws)
case SORT_DESCENDING: case SORT_DESCENDING:
if (ws.name_as_int >= 0) { 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) { if (it->item.name_as_int < ws.name_as_int) {
tll_insert_before(m->workspaces, it, ws); tll_insert_before(m->workspaces, it, ws);
return; return;
} }
} }
} else { } else {
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{
if (it->item.name_as_int >= 0) if (it->item.name_as_int >= 0)
continue; continue;
if (strcoll(it->item.name, ws.name) < 0) { if (strcoll(it->item.name, ws.name) < 0) {
@ -251,13 +191,12 @@ workspace_add(struct private *m, struct workspace ws)
} }
static void 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; struct workspace *ws = &it->item;
if (ws->id != id) if (strcmp(ws->name, name) != 0)
continue; continue;
workspace_free(ws); workspace_free(ws);
@ -267,22 +206,9 @@ workspace_del(struct private *m, int id)
} }
static struct workspace * static struct workspace *
workspace_lookup(struct private *m, int id) workspace_lookup(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)
return ws;
}
return NULL;
}
static struct workspace *
workspace_lookup_by_name(struct private *m, const char *name)
{
tll_foreach(m->workspaces, it)
{
struct workspace *ws = &it->item; struct workspace *ws = &it->item;
if (strcmp(ws->name, name) == 0) if (strcmp(ws->name, name) == 0)
return ws; return ws;
@ -291,7 +217,7 @@ workspace_lookup_by_name(struct private *m, const char *name)
} }
static bool 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; struct json_object *version;
if (!json_object_object_get_ex(json, "human_readable", &version)) { if (!json_object_object_get_ex(json, "human_readable", &version)) {
@ -304,7 +230,7 @@ handle_get_version_reply(int sock, int type, const struct json_object *json, voi
} }
static bool 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; struct json_object *success;
if (!json_object_object_get_ex(json, "success", &success)) { if (!json_object_object_get_ex(json, "success", &success)) {
@ -323,37 +249,12 @@ handle_subscribe_reply(int sock, int type, const struct json_object *json, void
static bool static bool
workspace_update_or_add(struct private *m, const struct json_object *ws_json) workspace_update_or_add(struct private *m, const struct json_object *ws_json)
{ {
struct json_object *_id; struct json_object *name;
if (!json_object_object_get_ex(ws_json, "id", &_id)) if (!json_object_object_get_ex(ws_json, "name", &name))
return false; return false;
const int id = json_object_get_int(_id); const char *name_as_string = json_object_get_string(name);
struct workspace *already_exists = workspace_lookup(m, id); struct workspace *already_exists = workspace_lookup(m, name_as_string);
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) { if (already_exists != NULL) {
bool persistent = already_exists->persistent; bool persistent = already_exists->persistent;
@ -375,7 +276,7 @@ workspace_update_or_add(struct private *m, const struct json_object *ws_json)
} }
static bool 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 module *mod = _mod;
struct private *m = mod->private; struct private *m = mod->private;
@ -402,7 +303,7 @@ err:
} }
static bool 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 module *mod = _mod;
struct private *m = mod->private; struct private *m = mod->private;
@ -418,20 +319,23 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
bool is_init = strcmp(change_str, "init") == 0; bool is_init = strcmp(change_str, "init") == 0;
bool is_empty = strcmp(change_str, "empty") == 0; bool is_empty = strcmp(change_str, "empty") == 0;
bool is_focused = strcmp(change_str, "focus") == 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_urgent = strcmp(change_str, "urgent") == 0;
bool is_reload = strcmp(change_str, "reload") == 0; bool is_reload = strcmp(change_str, "reload") == 0;
struct json_object *current, *_current_id; if (is_reload) {
if ((!json_object_object_get_ex(json, "current", &current) LOG_WARN("unimplemented: 'reload' event");
|| !json_object_object_get_ex(current, "id", &_current_id)) return true;
&& !is_reload) { }
LOG_ERR("workspace event without 'current' and/or 'id' properties");
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; 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); mtx_lock(&mod->lock);
@ -441,34 +345,36 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
} }
else if (is_empty) { else if (is_empty) {
struct workspace *ws = workspace_lookup(m, current_id); struct workspace *ws = workspace_lookup(m, current_name);
assert(ws != NULL); assert(ws != NULL);
if (!ws->persistent) if (!ws->persistent)
workspace_del(m, current_id); workspace_del(m, current_name);
else { else {
workspace_free_persistent(ws); workspace_free(ws);
ws->empty = true; ws->name = strdup(current_name);
assert(ws->persistent);
} }
} }
else if (is_focused) { else if (is_focused) {
struct json_object *old, *_old_id, *urgent; struct json_object *old, *_old_name, *urgent;
if (!json_object_object_get_ex(json, "old", &old) || !json_object_object_get_ex(old, "id", &_old_id) if (!json_object_object_get_ex(json, "old", &old) ||
|| !json_object_object_get_ex(current, "urgent", &urgent)) { !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"); LOG_ERR("workspace 'focused' event without 'old', 'name' and/or 'urgent' property");
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return false; return false;
} }
struct workspace *w = workspace_lookup(m, current_id); struct workspace *w = workspace_lookup(m, current_name);
assert(w != NULL); assert(w != NULL);
LOG_DBG("w: %s", w->name); LOG_DBG("w: %s", w->name);
/* Mark all workspaces on current's output invisible */ /* Mark all workspaces on current's output invisible */
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{
struct workspace *ws = &it->item; struct workspace *ws = &it->item;
if (ws->output != NULL && strcmp(ws->output, w->output) == 0) if (ws->output != NULL && strcmp(ws->output, w->output) == 0)
ws->visible = false; ws->visible = false;
@ -479,67 +385,12 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
w->visible = true; w->visible = true;
/* Old workspace is no longer focused */ /* Old workspace is no longer focused */
int old_id = json_object_get_int(_old_id); const char *old_name = json_object_get_string(_old_name);
struct workspace *old_w = workspace_lookup(m, old_id); struct workspace *old_w = workspace_lookup(m, old_name);
if (old_w != NULL) if (old_w != NULL)
old_w->focused = false; 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) { else if (is_urgent) {
struct json_object *urgent; struct json_object *urgent;
if (!json_object_object_get_ex(current, "urgent", &urgent)) { if (!json_object_object_get_ex(current, "urgent", &urgent)) {
@ -548,20 +399,10 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
return false; 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); 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; m->dirty = true;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return true; return true;
@ -572,7 +413,7 @@ err:
} }
static bool 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 module *mod = _mod;
struct private *m = mod->private; struct private *m = mod->private;
@ -594,9 +435,8 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
struct workspace *ws = NULL; struct workspace *ws = NULL;
__attribute__((unused)) size_t focused = 0; size_t focused = 0;
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{
if (it->item.focused) { if (it->item.focused) {
ws = &it->item; ws = &it->item;
focused++; focused++;
@ -606,20 +446,6 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
assert(focused == 1); assert(focused == 1);
assert(ws != NULL); 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) { if (is_close) {
free(ws->window.title); free(ws->window.title);
free(ws->window.application); free(ws->window.application);
@ -631,6 +457,23 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
m->dirty = true; m->dirty = true;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return true; 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); free(ws->window.title);
@ -651,24 +494,27 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
struct json_object *app_id; struct json_object *app_id;
struct json_object *pid; 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); free(ws->window.application);
ws->window.application = strdup(json_object_get_string(app_id)); ws->window.application = strdup(json_object_get_string(app_id));
LOG_DBG("application: \"%s\", via 'app_id'", ws->window.application); LOG_DBG("application: \"%s\", via 'app_id'", ws->window.application);
} }
/* If PID has changed, update application name from /proc/<pid>/comm */ /* 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); ws->window.pid = json_object_get_int(pid);
char path[64]; char path[64];
snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid); 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) { if (fd == -1) {
/* Application may simply have terminated */ /* Application may simply have terminated */
free(ws->window.application); free(ws->window.application); ws->window.application = NULL;
ws->window.application = NULL;
ws->window.pid = -1; ws->window.pid = -1;
m->dirty = true; m->dirty = true;
@ -693,7 +539,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
} }
static bool 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 module *mod = _mod;
struct private *m = mod->private; struct private *m = mod->private;
@ -735,7 +581,7 @@ run(struct module *mod)
if (!i3_get_socket_address(&addr)) if (!i3_get_socket_address(&addr))
return 1; return 1;
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) { if (sock == -1) {
LOG_ERRNO("failed to create UNIX socket"); LOG_ERRNO("failed to create UNIX socket");
return 1; return 1;
@ -752,19 +598,10 @@ run(struct module *mod)
for (size_t i = 0; i < m->persistent_count; i++) { for (size_t i = 0; i < m->persistent_count; i++) {
const char *name_as_string = m->persistent_workspaces[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 = { struct workspace ws = {
.id = -1,
.name = strdup(name_as_string), .name = strdup(name_as_string),
.name_as_int = name_as_int, .name_as_int = workspace_name_as_int(name_as_string),
.persistent = true, .persistent = true,
.empty = true,
}; };
workspace_add(m, ws); workspace_add(m, ws);
} }
@ -824,7 +661,7 @@ ws_content_for_name(struct private *m, const char *name)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
return "i3/sway"; return "i3/sway";
} }
@ -840,47 +677,30 @@ content(struct module *mod)
struct exposable *particles[tll_length(m->workspaces) + 1]; struct exposable *particles[tll_length(m->workspaces) + 1];
struct exposable *current = NULL; struct exposable *current = NULL;
tll_foreach(m->workspaces, it) tll_foreach(m->workspaces, it) {
{
struct workspace *ws = &it->item; struct workspace *ws = &it->item;
const struct ws_content *template = NULL; const struct ws_content *template = NULL;
/* Lookup content template for workspace. Fall back to default /* Lookup content template for workspace. Fall back to default
* template if this workspace doesn't have a specific * template if this workspace doesn't have a specific
* template */ * template */
if (ws->name == NULL) {
LOG_ERR("%d %d", ws->name_as_int, ws->id);
}
template = ws_content_for_name(m, ws->name); template = ws_content_for_name(m, ws->name);
if (template == NULL) { if (template == NULL) {
LOG_DBG("no ws template for %s, using default template", ws->name); LOG_DBG("no ws template for %s, using default template", ws->name);
template = ws_content_for_name(m, ""); template = ws_content_for_name(m, "");
} }
const char *state = ws->urgent ? "urgent" : ws->visible ? ws->focused ? "focused" : "unfocused" : "invisible"; const char *state =
ws->urgent ? "urgent" :
LOG_DBG("name=%s (name-as-int=%d): visible=%s, focused=%s, urgent=%s, empty=%s, state=%s, " ws->visible ? ws->focused ? "focused" : "unfocused" :
"application=%s, title=%s, mode=%s", "invisible";
ws->name, ws->name_as_int, ws->visible ? "yes" : "no", ws->focused ? "yes" : "no",
ws->urgent ? "yes" : "no", ws->empty ? "yes" : "no", state, ws->window.application, ws->window.title,
m->mode);
const char *name = ws->name;
if (m->strip_workspace_numbers) {
const char *colon = strchr(name, ':');
if (colon != NULL)
name = colon + 1;
}
struct tag_set tags = { struct tag_set tags = {
.tags = (struct tag *[]){ .tags = (struct tag *[]){
tag_new_string(mod, "name", name), tag_new_string(mod, "name", ws->name),
tag_new_string(mod, "output", ws->output),
tag_new_bool(mod, "visible", ws->visible), tag_new_bool(mod, "visible", ws->visible),
tag_new_bool(mod, "focused", ws->focused), tag_new_bool(mod, "focused", ws->focused),
tag_new_bool(mod, "urgent", ws->urgent), tag_new_bool(mod, "urgent", ws->urgent),
tag_new_bool(mod, "empty", ws->empty),
tag_new_string(mod, "state", state), tag_new_string(mod, "state", state),
tag_new_string(mod, "application", ws->window.application), tag_new_string(mod, "application", ws->window.application),
@ -888,7 +708,7 @@ content(struct module *mod)
tag_new_string(mod, "mode", m->mode), tag_new_string(mod, "mode", m->mode),
}, },
.count = 10, .count = 8,
}; };
if (ws->focused) { if (ws->focused) {
@ -898,9 +718,12 @@ content(struct module *mod)
} }
if (template == NULL) { 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 { } else {
particles[particle_count++] = template->content->instantiate(template->content, &tags); particles[particle_count++] = template->content->instantiate(
template->content, &tags);
} }
tag_set_destroy(&tags); tag_set_destroy(&tags);
@ -910,7 +733,8 @@ content(struct module *mod)
particles[particle_count++] = current; particles[particle_count++] = current;
mtx_unlock(&mod->lock); 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. */ /* Maps workspace name to a content particle. */
@ -920,9 +744,10 @@ struct i3_workspaces {
}; };
static struct module * static struct module *
i3_new(struct i3_workspaces workspaces[], size_t workspace_count, int left_spacing, int right_spacing, i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
enum sort_mode sort_mode, size_t persistent_count, const char *persistent_workspaces[static persistent_count], int left_spacing, int right_spacing, enum sort_mode sort_mode,
bool strip_workspace_numbers) size_t persistent_count,
const char *persistent_workspaces[static persistent_count])
{ {
struct private *m = calloc(1, sizeof(*m)); struct private *m = calloc(1, sizeof(*m));
@ -938,11 +763,11 @@ i3_new(struct i3_workspaces workspaces[], size_t workspace_count, int left_spaci
m->ws_content.v[i].content = workspaces[i].content; m->ws_content.v[i].content = workspaces[i].content;
} }
m->strip_workspace_numbers = strip_workspace_numbers;
m->sort_mode = sort_mode; m->sort_mode = sort_mode;
m->persistent_count = persistent_count; m->persistent_count = persistent_count;
m->persistent_workspaces = calloc(persistent_count, sizeof(m->persistent_workspaces[0])); m->persistent_workspaces = calloc(
persistent_count, sizeof(m->persistent_workspaces[0]));
for (size_t i = 0; i < persistent_count; i++) for (size_t i = 0; i < persistent_count; i++)
m->persistent_workspaces[i] = strdup(persistent_workspaces[i]); m->persistent_workspaces[i] = strdup(persistent_workspaces[i]);
@ -965,26 +790,28 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *right_spacing = yml_get_value(node, "right-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 *sort = yml_get_value(node, "sort");
const struct yml_node *persistent = yml_get_value(node, "persistent"); 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 left = spacing != NULL ? yml_value_as_int(spacing) :
int right = spacing != NULL ? yml_value_as_int(spacing) left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
: right_spacing != NULL ? yml_value_as_int(right_spacing) int right = spacing != NULL ? yml_value_as_int(spacing) :
: 0; right_spacing != NULL ? yml_value_as_int(right_spacing) : 0;
const char *sort_value = sort != NULL ? yml_value_as_string(sort) : NULL; const char *sort_value = sort != NULL ? yml_value_as_string(sort) : NULL;
enum sort_mode sort_mode = sort_value == NULL ? SORT_NONE enum sort_mode sort_mode =
: strcmp(sort_value, "none") == 0 ? SORT_NONE sort_value == NULL ? SORT_NONE :
: strcmp(sort_value, "native") == 0 ? SORT_NATIVE strcmp(sort_value, "none") == 0 ? SORT_NONE :
: strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING : SORT_DESCENDING;
: SORT_DESCENDING;
const size_t persistent_count = persistent != NULL ? yml_list_length(persistent) : 0; const size_t persistent_count =
persistent != NULL ? yml_list_length(persistent) : 0;
const char *persistent_workspaces[persistent_count]; const char *persistent_workspaces[persistent_count];
if (persistent != NULL) { if (persistent != NULL) {
size_t idx = 0; size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(persistent); it.node != NULL; yml_list_next(&it), idx++) { 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); persistent_workspaces[idx] = yml_value_as_string(it.node);
} }
} }
@ -992,27 +819,36 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
struct i3_workspaces workspaces[yml_dict_length(c)]; struct i3_workspaces workspaces[yml_dict_length(c)];
size_t idx = 0; 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].name = yml_value_as_string(it.key);
workspaces[idx].content = conf_to_particle(it.value, inherited); 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, return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode,
(strip_workspace_number != NULL ? yml_value_as_bool(strip_workspace_number) : false)); persistent_count, persistent_workspaces);
} }
static bool static bool
verify_content(keychain_t *chain, const struct yml_node *node) verify_content(keychain_t *chain, const struct yml_node *node)
{ {
if (!yml_is_dict(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; 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); const char *key = yml_value_as_string(it.key);
if (key == NULL) { 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; return false;
} }
@ -1028,7 +864,8 @@ verify_content(keychain_t *chain, const struct yml_node *node)
static bool static bool
verify_sort(keychain_t *chain, const struct yml_node *node) verify_sort(keychain_t *chain, const struct yml_node *node)
{ {
return conf_verify_enum(chain, node, (const char *[]){"none", "native", "ascending", "descending"}, 4); return conf_verify_enum(
chain, node, (const char *[]){"none", "ascending", "descending"}, 3);
} }
static bool static bool
@ -1041,12 +878,11 @@ static bool
verify_conf(keychain_t *chain, const struct yml_node *node) verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"spacing", false, &conf_verify_unsigned}, {"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_int},
{"sort", false, &verify_sort}, {"sort", false, &verify_sort},
{"persistent", false, &verify_persistent}, {"persistent", false, &verify_persistent},
{"strip-workspace-numbers", false, &conf_verify_bool},
{"content", true, &verify_content}, {"content", true, &verify_content},
{"anchors", false, NULL}, {"anchors", false, NULL},
{NULL, false, NULL}, {NULL, false, NULL},
@ -1061,5 +897,5 @@ const struct module_iface module_i3_iface = {
}; };
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) #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 #endif

View file

@ -1,14 +1,16 @@
#include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h>
#include <poll.h> #include <poll.h>
#include "../config-verify.h"
#include "../config.h" #include "../config.h"
#include "../config-verify.h"
#include "../module.h" #include "../module.h"
#include "../plugin.h" #include "../plugin.h"
struct private { struct particle *label; }; struct private {
struct particle *label;
};
static void static void
destroy(struct module *mod) destroy(struct module *mod)
@ -20,7 +22,7 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
return "label"; 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 = [] modules = []
# Optional deps alsa = dependency('alsa')
alsa = dependency('alsa', required: get_option('plugin-alsa')) udev = dependency('libudev')
plugin_alsa_enabled = alsa.found() json = dependency('json-c')
mpd = dependency('libmpdclient')
udev_backlight = dependency('libudev', required: get_option('plugin-backlight')) xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
plugin_backlight_enabled = udev_backlight.found()
udev_battery = dependency('libudev', required: get_option('plugin-battery'))
plugin_battery_enabled = udev_battery.found()
plugin_clock_enabled = get_option('plugin-clock').allowed()
plugin_cpu_enabled = get_option('plugin-cpu').allowed()
plugin_disk_io_enabled = get_option('plugin-disk-io').allowed()
plugin_dwl_enabled = get_option('plugin-dwl').allowed()
plugin_foreign_toplevel_enabled = backend_wayland and get_option('plugin-foreign-toplevel').allowed()
plugin_mem_enabled = get_option('plugin-mem').allowed()
mpd = dependency('libmpdclient', required: get_option('plugin-mpd'))
plugin_mpd_enabled = mpd.found()
# DBus dependency. Used by 'mpris'
sdbus_library = dependency('libsystemd', 'libelogind', 'basu', required: get_option('plugin-mpris'))
plugin_mpris_enabled = sdbus_library.found()
json_i3 = dependency('json-c', required: get_option('plugin-i3'))
plugin_i3_enabled = json_i3.found()
plugin_label_enabled = get_option('plugin-label').allowed()
plugin_network_enabled = get_option('plugin-network').allowed()
pipewire = dependency('libpipewire-0.3', required: get_option('plugin-pipewire'))
json_pipewire = dependency('json-c', required: get_option('plugin-pipewire'))
plugin_pipewire_enabled = pipewire.found() and json_pipewire.found()
pulse = dependency('libpulse', required: get_option('plugin-pulse'))
plugin_pulse_enabled = pulse.found()
udev_removables = dependency('libudev', required: get_option('plugin-removables'))
plugin_removables_enabled = udev_removables.found()
plugin_river_enabled = backend_wayland and get_option('plugin-river').allowed()
plugin_script_enabled = get_option('plugin-script').allowed()
json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb'))
plugin_sway_xkb_enabled = json_sway_xkb.found()
json_niri_language = dependency('json-c', required: get_option('plugin-niri-language'))
plugin_niri_language_enabled = json_niri_language.found()
json_niri_workspaces = dependency('json-c', required: get_option('plugin-niri-workspaces'))
plugin_niri_workspaces_enabled = json_niri_workspaces.found()
xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb'))
plugin_xkb_enabled = backend_x11 and xcb_xkb.found()
plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed()
# Module name -> (source-list, dep-list) # 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 if backend_x11
mod_data += {'alsa': [[], [m, alsa]]} mod_data += {
'xkb': [[], [xcb_stuff, xcb_xkb]],
'xwindow': [[], [xcb_stuff]],
}
endif endif
if plugin_backlight_enabled if backend_wayland
mod_data += {'backlight': [[], [m, udev_backlight]]}
endif
if plugin_battery_enabled
mod_data += {'battery': [[], [udev_battery]]}
endif
if plugin_clock_enabled
mod_data += {'clock': [[], []]}
endif
if plugin_cpu_enabled
mod_data += {'cpu': [[], [m, dynlist]]}
endif
if plugin_disk_io_enabled
mod_data += {'disk-io': [[], [dynlist]]}
endif
if plugin_dwl_enabled
mod_data += {'dwl': [[], [dynlist]]}
endif
if plugin_mem_enabled
mod_data += {'mem': [[], [m]]}
endif
if plugin_mpd_enabled
mod_data += {'mpd': [[], [mpd]]}
endif
if plugin_mpris_enabled
sdbus = declare_dependency(compile_args: ['-DHAVE_' + sdbus_library.name().to_upper()], dependencies:[sdbus_library])
mod_data += {'mpris': [[], [sdbus]]}
endif
if plugin_i3_enabled
mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]}
endif
if plugin_label_enabled
mod_data += {'label': [[], []]}
endif
if plugin_network_enabled
mod_data += {'network': [[], [dynlist]]}
endif
if plugin_pipewire_enabled
mod_data += {'pipewire': [[], [m, pipewire, dynlist, json_pipewire]]}
endif
if plugin_pulse_enabled
mod_data += {'pulse': [[], [m, pulse]]}
endif
if plugin_removables_enabled
mod_data += {'removables': [[], [dynlist, udev_removables]]}
endif
if plugin_script_enabled
mod_data += {'script': [[], []]}
endif
if plugin_sway_xkb_enabled
mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]}
endif
if plugin_niri_language_enabled
mod_data += {'niri-language': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_language]]}
endif
if plugin_niri_workspaces_enabled
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
endif
if plugin_xkb_enabled
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
endif
if plugin_xwindow_enabled
mod_data += {'xwindow': [[], [xcb_stuff]]}
endif
if plugin_river_enabled
river_proto_headers = [] river_proto_headers = []
river_proto_src = [] river_proto_src = []
@ -171,10 +49,10 @@ if plugin_river_enabled
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach endforeach
mod_data += {'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist, wayland_client]]} mod_data += {
endif 'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]],
}
if plugin_foreign_toplevel_enabled
ftop_proto_headers = [] ftop_proto_headers = []
ftop_proto_src = [] ftop_proto_src = []
@ -193,7 +71,9 @@ if plugin_foreign_toplevel_enabled
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach endforeach
mod_data += {'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [m, dynlist, wayland_client]]} mod_data += {
'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [dynlist]],
}
endif endif
foreach mod, data : mod_data 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 <assert.h>
#include <errno.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 <poll.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/eventfd.h> #include <sys/eventfd.h>
#include <sys/inotify.h> #include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <mpd/client.h> #include <mpd/client.h>
#define LOG_MODULE "mpd" #define LOG_MODULE "mpd"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h" #include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h" #include "../plugin.h"
struct private struct private {
{
char *host; char *host;
uint16_t port; uint16_t port;
struct particle *label; struct particle *label;
@ -39,12 +38,10 @@ struct private
bool repeat; bool repeat;
bool random; bool random;
bool consume; bool consume;
bool single; int volume;
int volume;
char *album; char *album;
char *artist; char *artist;
char *title; char *title;
char *file;
struct { struct {
uint64_t value; uint64_t value;
@ -62,9 +59,11 @@ destroy(struct module *mod)
struct private *m = mod->private; struct private *m = mod->private;
if (m->refresh_thread_id != 0) { if (m->refresh_thread_id != 0) {
assert(m->refresh_abort_fd != -1); 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"); LOG_ERRNO("failed to signal abort to refresher thread");
} else { } else{
int res; int res;
thrd_join(m->refresh_thread_id, &res); thrd_join(m->refresh_thread_id, &res);
} }
@ -76,7 +75,6 @@ destroy(struct module *mod)
free(m->album); free(m->album);
free(m->artist); free(m->artist);
free(m->title); free(m->title);
free(m->file);
assert(m->conn == NULL); assert(m->conn == NULL);
m->label->destroy(m->label); m->label->destroy(m->label);
@ -86,7 +84,7 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
return "mpd"; return "mpd";
} }
@ -132,11 +130,12 @@ content(struct module *mod)
if (m->state == MPD_STATE_PLAY) { if (m->state == MPD_STATE_PLAY) {
elapsed += timespec_diff_milli_seconds(&now, &m->elapsed.when); elapsed += timespec_diff_milli_seconds(&now, &m->elapsed.when);
if (elapsed > m->duration) { if (elapsed > m->duration) {
LOG_DBG("dynamic update of elapsed overflowed: " LOG_DBG(
"elapsed=%" PRIu64 ", duration=%" PRIu64, "dynamic update of elapsed overflowed: "
elapsed, m->duration); "elapsed=%"PRIu64", duration=%"PRIu64, elapsed, m->duration);
elapsed = m->duration; elapsed = m->duration;
} }
} }
unsigned elapsed_secs = elapsed / 1000; unsigned elapsed_secs = elapsed / 1000;
@ -153,23 +152,16 @@ content(struct module *mod)
state_str = "offline"; state_str = "offline";
else { else {
switch (m->state) { switch (m->state) {
case MPD_STATE_UNKNOWN: case MPD_STATE_UNKNOWN: state_str = "unknown"; break;
state_str = "unknown"; case MPD_STATE_STOP: state_str = "stopped"; break;
break; case MPD_STATE_PAUSE: state_str = "paused"; break;
case MPD_STATE_STOP: case MPD_STATE_PLAY: state_str = "playing"; break;
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? */ /* 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 = { struct tag_set tags = {
.tags = (struct tag *[]){ .tags = (struct tag *[]){
@ -177,19 +169,17 @@ content(struct module *mod)
tag_new_bool(mod, "repeat", m->repeat), tag_new_bool(mod, "repeat", m->repeat),
tag_new_bool(mod, "random", m->random), tag_new_bool(mod, "random", m->random),
tag_new_bool(mod, "consume", m->consume), 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_int_range(mod, "volume", m->volume, 0, 100),
tag_new_string(mod, "album", m->album), tag_new_string(mod, "album", m->album),
tag_new_string(mod, "artist", m->artist), tag_new_string(mod, "artist", m->artist),
tag_new_string(mod, "title", m->title), tag_new_string(mod, "title", m->title),
tag_new_string(mod, "file", m->file),
tag_new_string(mod, "pos", pos), tag_new_string(mod, "pos", pos),
tag_new_string(mod, "end", end), tag_new_string(mod, "end", end),
tag_new_int(mod, "duration", m->duration), tag_new_int(mod, "duration", m->duration),
tag_new_int_realtime( tag_new_int_realtime(
mod, "elapsed", elapsed, 0, m->duration, realtime), mod, "elapsed", elapsed, 0, m->duration, realtime),
}, },
.count = 14, .count = 12,
}; };
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
@ -233,7 +223,7 @@ wait_for_socket_create(const struct module *mod)
struct stat st; struct stat st;
if (stat(m->host, &st) == 0 && S_ISSOCK(st.st_mode)) { 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}; struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1); 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); LOG_DBG("%s: already exists, and is connectable", m->host);
have_mpd_socket = true; have_mpd_socket = true;
} else { } 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); close(s);
@ -255,15 +246,12 @@ wait_for_socket_create(const struct module *mod)
bool ret = false; bool ret = false;
while (!have_mpd_socket) { 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) { poll(fds, 2, -1);
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if (fds[0].revents & POLLIN) { if (fds[0].revents & POLLIN) {
ret = true; ret = true;
@ -275,7 +263,7 @@ wait_for_socket_create(const struct module *mod)
char buf[1024]; char buf[1024];
ssize_t len = read(fd, buf, sizeof(buf)); 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; const struct inotify_event *e = (const struct inotify_event *)ptr;
LOG_DBG("inotify: CREATED: %s/%.*s", directory, e->len, e->name); LOG_DBG("inotify: CREATED: %s/%.*s", directory, e->len, e->name);
@ -285,7 +273,7 @@ wait_for_socket_create(const struct module *mod)
break; 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); enum mpd_error merr = mpd_connection_get_error(conn);
if (merr != MPD_ERROR_SUCCESS) { 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); mpd_connection_free(conn);
return NULL; return NULL;
} }
@ -326,7 +315,8 @@ update_status(struct module *mod)
struct mpd_status *status = mpd_run_status(m->conn); struct mpd_status *status = mpd_run_status(m->conn);
if (status == NULL) { 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; return false;
} }
@ -338,7 +328,6 @@ update_status(struct module *mod)
m->repeat = mpd_status_get_repeat(status); m->repeat = mpd_status_get_repeat(status);
m->random = mpd_status_get_random(status); m->random = mpd_status_get_random(status);
m->consume = mpd_status_get_consume(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->volume = mpd_status_get_volume(status);
m->duration = mpd_status_get_total_time(status) * 1000; m->duration = mpd_status_get_total_time(status) * 1000;
m->elapsed.value = mpd_status_get_elapsed_ms(status); 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); struct mpd_song *song = mpd_run_current_song(m->conn);
if (song == NULL && mpd_connection_get_error(m->conn) != MPD_ERROR_SUCCESS) { 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; return false;
} }
if (song == NULL) { if (song == NULL) {
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
free(m->album); free(m->album); m->album = NULL;
m->album = NULL; free(m->artist); m->artist = NULL;
free(m->artist); free(m->title); m->title = NULL;
m->artist = NULL;
free(m->title);
m->title = NULL;
free(m->file);
m->file = NULL;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
} else { } else {
const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); 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 *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 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); mtx_lock(&mod->lock);
free(m->album); free(m->album);
free(m->artist); free(m->artist);
free(m->title); free(m->title);
free(m->file);
m->album = album != NULL ? strdup(album) : NULL; m->album = album != NULL ? strdup(album) : NULL;
m->artist = artist != NULL ? strdup(artist) : NULL; m->artist = artist != NULL ? strdup(artist) : NULL;
m->title = title != NULL ? strdup(title) : NULL; m->title = title != NULL ? strdup(title) : NULL;
m->file = file != NULL ? strdup(file) : NULL;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
mpd_song_free(song); mpd_song_free(song);
@ -395,9 +377,8 @@ run(struct module *mod)
struct private *m = mod->private; struct private *m = mod->private;
bool aborted = false; bool aborted = false;
int ret = 0;
while (!aborted && ret == 0) { while (!aborted) {
if (m->conn != NULL) { if (m->conn != NULL) {
mpd_connection_free(m->conn); mpd_connection_free(m->conn);
@ -406,21 +387,16 @@ run(struct module *mod)
/* Reset state */ /* Reset state */
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
free(m->album); free(m->album); m->album = NULL;
m->album = NULL; free(m->artist); m->artist = NULL;
free(m->artist); free(m->title); m->title = NULL;
m->artist = NULL;
free(m->title);
m->title = NULL;
free(m->file);
m->file = NULL;
m->state = MPD_STATE_UNKNOWN; m->state = MPD_STATE_UNKNOWN;
m->elapsed.value = m->duration = 0; m->elapsed.value = m->duration = 0;
m->elapsed.when.tv_sec = m->elapsed.when.tv_nsec = 0; m->elapsed.when.tv_sec = m->elapsed.when.tv_nsec = 0;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
/* Keep trying to connect, until we succeed */ /* Keep trying to connect, until we succeed */
while (!aborted && ret == 0) { while (!aborted) {
if (m->port == 0) { if (m->port == 0) {
/* Use inotify to watch for socket creation */ /* Use inotify to watch for socket creation */
aborted = wait_for_socket_create(mod); 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 * host), wait for a while until we try to re-connect
* again. * again.
*/ */
while (!aborted) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; int res = poll(fds, 1, 10 * 1000);
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 2 * 1000);
if (res < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
ret = 1;
break;
}
if (res == 0) {
ret = 0;
break;
}
else if (res == 1) {
assert(fds[0].revents & POLLIN);
aborted = true;
}
if (res == 1) {
assert(fds[0].revents & POLLIN);
aborted = true;
} }
} }
if (aborted || ret != 0) if (aborted)
break; break;
/* Initial state (after establishing a connection) */ /* Initial state (after establishing a connection) */
@ -482,18 +441,12 @@ run(struct module *mod)
}; };
if (!mpd_send_idle(m->conn)) { 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; break;
} }
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { poll(fds, 2, -1);
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
ret = 1;
break;
}
if (fds[0].revents & POLLIN) { if (fds[0].revents & POLLIN) {
aborted = true; aborted = true;
@ -506,7 +459,8 @@ run(struct module *mod)
} }
if (fds[1].revents & POLLIN) { 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); LOG_DBG("IDLE mask: %d", idle);
@ -523,7 +477,7 @@ run(struct module *mod)
m->conn = NULL; m->conn = NULL;
} }
return aborted ? 0 : ret; return 0;
} }
struct refresh_context { struct refresh_context {
@ -578,7 +532,9 @@ refresh_in(struct module *mod, long milli_seconds)
/* Signal abort to thread */ /* Signal abort to thread */
assert(m->refresh_abort_fd != -1); 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"); LOG_ERRNO("failed to signal abort to refresher thread");
return false; 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 */ /* Detach - we don't want to have to thrd_join() it */
// thrd_detach(tid); //thrd_detach(tid);
return r == 0; 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 *port = yml_get_value(node, "port");
const struct yml_node *c = yml_get_value(node, "content"); 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, return mpd_new(
conf_to_particle(c, inherited)); yml_value_as_string(host),
port != NULL ? yml_value_as_int(port) : 0,
conf_to_particle(c, inherited));
} }
static bool static bool
@ -658,7 +616,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"host", true, &conf_verify_string}, {"host", true, &conf_verify_string},
{"port", false, &conf_verify_unsigned}, {"port", false, &conf_verify_int},
MODULE_COMMON_ATTRS, 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

View file

@ -1,15 +1,14 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <poll.h> #include <poll.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h>
#include <libudev.h> #include <libudev.h>
@ -17,10 +16,10 @@
#define LOG_MODULE "removables" #define LOG_MODULE "removables"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h" #include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../particles/dynlist.h" #include "../particles/dynlist.h"
#include "../plugin.h" #include "../plugin.h"
@ -36,8 +35,8 @@ struct partition {
char *label; char *label;
uint64_t size; uint64_t size;
bool audio_cd;
/*tll(char *) mount_points;*/
mount_point_list_t mount_points; mount_point_list_t mount_points;
}; };
@ -54,8 +53,7 @@ struct block_device {
tll(struct partition) partitions; tll(struct partition) partitions;
}; };
struct private struct private {
{
struct particle *label; struct particle *label;
int left_spacing; int left_spacing;
int right_spacing; int right_spacing;
@ -76,7 +74,8 @@ free_partition(struct partition *p)
static void static void
free_device(struct block_device *b) free_device(struct block_device *b)
{ {
tll_foreach(b->partitions, it) free_partition(&it->item); tll_foreach(b->partitions, it)
free_partition(&it->item);
tll_free(b->partitions); tll_free(b->partitions);
free(b->sys_path); free(b->sys_path);
@ -91,7 +90,8 @@ destroy(struct module *mod)
struct private *m = mod->private; struct private *m = mod->private;
m->label->destroy(m->label); m->label->destroy(m->label);
tll_foreach(m->devices, it) free_device(&it->item); tll_foreach(m->devices, it)
free_device(&it->item);
tll_free(m->devices); tll_free(m->devices);
tll_free_and_free(m->ignore, free); tll_free_and_free(m->ignore, free);
@ -100,7 +100,7 @@ destroy(struct module *mod)
} }
static const char * static const char *
description(const struct module *mod) description(struct module *mod)
{ {
return "removables"; return "removables";
} }
@ -112,23 +112,24 @@ content(struct module *mod)
tll(const struct partition *) partitions = tll_init(); tll(const struct partition *) partitions = tll_init();
tll_foreach(m->devices, dev) tll_foreach(m->devices, dev) {
{ tll_foreach(dev->item.partitions, part) {
tll_foreach(dev->item.partitions, part) { tll_push_back(partitions, &part->item); } tll_push_back(partitions, &part->item);
}
} }
struct exposable *exposables[max(tll_length(partitions), 1)]; struct exposable *exposables[max(tll_length(partitions), 1)];
size_t idx = 0; size_t idx = 0;
tll_foreach(partitions, it) tll_foreach(partitions, it) {
{
const struct partition *p = it->item; const struct partition *p = it->item;
char dummy_label[16]; char dummy_label[16];
const char *label = p->label; const char *label = p->label;
if (label == NULL) { if (label == NULL) {
snprintf(dummy_label, sizeof(dummy_label), "%.1f GB", (double)p->size / 1024 / 1024 / 1024 * 512); snprintf(dummy_label, sizeof(dummy_label),
"%.1f GB", (double)p->size / 1024 / 1024 / 1024 * 512);
label = dummy_label; label = dummy_label;
} }
@ -140,14 +141,13 @@ content(struct module *mod)
tag_new_string(mod, "vendor", p->block->vendor), tag_new_string(mod, "vendor", p->block->vendor),
tag_new_string(mod, "model", p->block->model), tag_new_string(mod, "model", p->block->model),
tag_new_bool(mod, "optical", p->block->optical), tag_new_bool(mod, "optical", p->block->optical),
tag_new_bool(mod, "audio", p->audio_cd),
tag_new_string(mod, "device", p->dev_path), tag_new_string(mod, "device", p->dev_path),
tag_new_int_range(mod, "size", p->size, 0, p->block->size), tag_new_int_range(mod, "size", p->size, 0, p->block->size),
tag_new_string(mod, "label", label), tag_new_string(mod, "label", label),
tag_new_bool(mod, "mounted", is_mounted), tag_new_bool(mod, "mounted", is_mounted),
tag_new_string(mod, "mount_point", mount_point), tag_new_string(mod, "mount_point", mount_point),
}, },
.count = 9, .count = 8,
}; };
exposables[idx++] = m->label->instantiate(m->label, &tags); exposables[idx++] = m->label->instantiate(m->label, &tags);
@ -155,24 +155,24 @@ content(struct module *mod)
} }
tll_free(partitions); tll_free(partitions);
return dynlist_exposable_new(exposables, idx, m->left_spacing, m->right_spacing); return dynlist_exposable_new(
exposables, idx, m->left_spacing, m->right_spacing);
} }
static void static void
find_mount_points(const char *dev_path, mount_point_list_t *mount_points) find_mount_points(const char *dev_path, mount_point_list_t *mount_points)
{ {
FILE *f = fopen("/proc/self/mountinfo", "re"); FILE *f = fopen("/proc/self/mountinfo", "r");
assert(f != NULL);
if (f == NULL) {
LOG_ERRNO("failed to open /proc/self/mountinfo");
return;
}
char line[4096]; char line[4096];
while (fgets(line, sizeof(line), f) != NULL) { while (fgets(line, sizeof(line), f) != NULL) {
char *dev = NULL, *path = NULL; char *dev = NULL, *path = NULL;
if (sscanf(line, "%*u %*u %*u:%*u %*s %ms %*[^-] - %*s %ms %*s", &path, &dev) != 2) { if (sscanf(line, "%*u %*u %*u:%*u %*s %ms %*[^-] - %*s %ms %*s",
&path, &dev) != 2)
{
LOG_ERR("failed to parse /proc/self/mountinfo: %s", line); LOG_ERR("failed to parse /proc/self/mountinfo: %s", line);
free(dev); free(dev);
free(path); free(path);
@ -199,11 +199,9 @@ update_mount_points(struct partition *partition)
/* Remove mount points that no longer exists (i.e. old mount /* Remove mount points that no longer exists (i.e. old mount
* points that aren't in the new list) */ * points that aren't in the new list) */
tll_foreach(partition->mount_points, old) tll_foreach(partition->mount_points, old) {
{
bool gone = true; bool gone = true;
tll_foreach(new_mounts, new) tll_foreach(new_mounts, new) {
{
if (strcmp(new->item, old->item) == 0) { if (strcmp(new->item, old->item) == 0) {
/* Remove from new list, as it's already in the /* Remove from new list, as it's already in the
* partitions list */ * partitions list */
@ -222,8 +220,7 @@ update_mount_points(struct partition *partition)
/* Add new mount points (i.e. mount points in the new list, that /* Add new mount points (i.e. mount points in the new list, that
* aren't in the old list) */ * aren't in the old list) */
tll_foreach(new_mounts, new) tll_foreach(new_mounts, new) {
{
LOG_DBG("%s: mounted on %s", partition->dev_path, new->item); LOG_DBG("%s: mounted on %s", partition->dev_path, new->item);
tll_push_back(partition->mount_points, new->item); tll_push_back(partition->mount_points, new->item);
@ -237,13 +234,14 @@ update_mount_points(struct partition *partition)
} }
static struct partition * static struct partition *
add_partition(struct module *mod, struct block_device *block, struct udev_device *dev) add_partition(struct module *mod, struct block_device *block,
struct udev_device *dev)
{ {
struct private *m = mod->private; struct private *m = mod->private;
const char *_size = udev_device_get_sysattr_value(dev, "size"); const char *_size = udev_device_get_sysattr_value(dev, "size");
uint64_t size = 0; uint64_t size = 0;
if (_size != NULL) if (_size != NULL)
sscanf(_size, "%" SCNu64, &size); sscanf(_size, "%"SCNu64, &size);
#if 0 #if 0
struct udev_list_entry *e = NULL; struct udev_list_entry *e = NULL;
@ -254,8 +252,7 @@ add_partition(struct module *mod, struct block_device *block, struct udev_device
const char *devname = udev_device_get_property_value(dev, "DEVNAME"); const char *devname = udev_device_get_property_value(dev, "DEVNAME");
if (devname != NULL) { if (devname != NULL) {
tll_foreach(m->ignore, it) tll_foreach(m->ignore, it) {
{
if (strcmp(it->item, devname) == 0) { if (strcmp(it->item, devname) == 0) {
LOG_DBG("ignoring %s because it is on the ignore list", devname); LOG_DBG("ignoring %s because it is on the ignore list", devname);
return NULL; return NULL;
@ -267,70 +264,20 @@ add_partition(struct module *mod, struct block_device *block, struct udev_device
if (label == NULL) if (label == NULL)
label = udev_device_get_property_value(dev, "ID_LABEL"); label = udev_device_get_property_value(dev, "ID_LABEL");
LOG_INFO("partition: add: %s: label=%s, size=%" PRIu64, udev_device_get_devnode(dev), label, size); LOG_INFO("partition: add: %s: label=%s, size=%"PRIu64,
udev_device_get_devnode(dev), label, size);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_push_back(block->partitions, ((struct partition){.block = block, tll_push_back(
.sys_path = strdup(udev_device_get_devpath(dev)), block->partitions,
.dev_path = strdup(udev_device_get_devnode(dev)), ((struct partition){
.label = label != NULL ? strdup(label) : NULL, .block = block,
.size = size, .sys_path = strdup(udev_device_get_devpath(dev)),
.audio_cd = false, .dev_path = strdup(udev_device_get_devnode(dev)),
.mount_points = tll_init()})); .label = label != NULL ? strdup(label) : NULL,
.size = size,
struct partition *p = &tll_back(block->partitions); .mount_points = tll_init()}));
update_mount_points(p);
mtx_unlock(&mod->lock);
return p;
}
static struct partition *
add_audio_cd(struct module *mod, struct block_device *block, struct udev_device *dev)
{
struct private *m = mod->private;
const char *_size = udev_device_get_sysattr_value(dev, "size");
uint64_t size = 0;
if (_size != NULL)
sscanf(_size, "%" SCNu64, &size);
#if 0
struct udev_list_entry *e = NULL;
udev_list_entry_foreach(e, udev_device_get_properties_list_entry(dev)) {
LOG_DBG("%s -> %s", udev_list_entry_get_name(e), udev_list_entry_get_value(e));
}
#endif
const char *devname = udev_device_get_property_value(dev, "DEVNAME");
if (devname != NULL) {
tll_foreach(m->ignore, it)
{
if (strcmp(it->item, devname) == 0) {
LOG_DBG("ignoring %s because it is on the ignore list", devname);
return NULL;
}
}
}
const char *_track_count = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
unsigned long track_count = strtoul(_track_count, NULL, 10);
char label[64];
snprintf(label, sizeof(label), "Audio CD - %lu tracks", track_count);
LOG_INFO("audio CD: add: %s: tracks=%lu, label=%s, size=%" PRIu64, udev_device_get_devnode(dev), track_count, label,
size);
mtx_lock(&mod->lock);
tll_push_back(block->partitions, ((struct partition){.block = block,
.sys_path = strdup(udev_device_get_devpath(dev)),
.dev_path = strdup(udev_device_get_devnode(dev)),
.label = label != NULL ? strdup(label) : NULL,
.size = size,
.audio_cd = true,
.mount_points = tll_init()}));
struct partition *p = &tll_back(block->partitions); struct partition *p = &tll_back(block->partitions);
update_mount_points(p); update_mount_points(p);
@ -340,15 +287,15 @@ add_audio_cd(struct module *mod, struct block_device *block, struct udev_device
} }
static bool static bool
del_partition(struct module *mod, struct block_device *block, struct udev_device *dev) del_partition(struct module *mod, struct block_device *block,
struct udev_device *dev)
{ {
const char *sys_path = udev_device_get_devpath(dev); const char *sys_path = udev_device_get_devpath(dev);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_foreach(block->partitions, it) tll_foreach(block->partitions, it) {
{
if (strcmp(it->item.sys_path, sys_path) == 0) { if (strcmp(it->item.sys_path, sys_path) == 0) {
LOG_INFO("%s: del: %s", it->item.audio_cd ? "audio CD" : "partition", it->item.dev_path); LOG_INFO("partition: del: %s", it->item.dev_path);
free_partition(&it->item); free_partition(&it->item);
tll_remove(block->partitions, it); tll_remove(block->partitions, it);
@ -377,8 +324,7 @@ add_device(struct module *mod, struct udev_device *dev)
const char *devname = udev_device_get_property_value(dev, "DEVNAME"); const char *devname = udev_device_get_property_value(dev, "DEVNAME");
if (devname != NULL) { if (devname != NULL) {
tll_foreach(m->ignore, it) tll_foreach(m->ignore, it) {
{
if (strcmp(it->item, devname) == 0) { if (strcmp(it->item, devname) == 0) {
LOG_DBG("ignoring %s because it is on the ignore list", devname); LOG_DBG("ignoring %s because it is on the ignore list", devname);
return NULL; return NULL;
@ -389,12 +335,11 @@ add_device(struct module *mod, struct udev_device *dev)
const char *_size = udev_device_get_sysattr_value(dev, "size"); const char *_size = udev_device_get_sysattr_value(dev, "size");
uint64_t size = 0; uint64_t size = 0;
if (_size != NULL) if (_size != NULL)
sscanf(_size, "%" SCNu64, &size); sscanf(_size, "%"SCNu64, &size);
#if 1 #if 1
struct udev_list_entry *e = NULL; struct udev_list_entry *e = NULL;
udev_list_entry_foreach(e, udev_device_get_properties_list_entry(dev)) udev_list_entry_foreach(e, udev_device_get_properties_list_entry(dev)) {
{
LOG_DBG("%s -> %s", udev_list_entry_get_name(e), udev_list_entry_get_value(e)); LOG_DBG("%s -> %s", udev_list_entry_get_name(e), udev_list_entry_get_value(e));
} }
#endif #endif
@ -405,38 +350,31 @@ add_device(struct module *mod, struct udev_device *dev)
const char *_optical = udev_device_get_property_value(dev, "ID_CDROM"); const char *_optical = udev_device_get_property_value(dev, "ID_CDROM");
bool optical = _optical != NULL && strcmp(_optical, "1") == 0; bool optical = _optical != NULL && strcmp(_optical, "1") == 0;
const char *_media = udev_device_get_property_value(dev, "ID_CDROM_MEDIA");
bool media = _media != NULL && strcmp(_media, "1") == 0;
const char *_fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE"); const char *_fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE");
bool have_fs = _fs_usage != NULL && strcmp(_fs_usage, "filesystem") == 0; bool media = _fs_usage != NULL && strcmp(_fs_usage, "filesystem") == 0;
const char *_audio_track_count = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO"); LOG_DBG("device: add: %s: vendor=%s, model=%s, optical=%d, size=%"PRIu64,
unsigned long audio_track_count = _audio_track_count != NULL ? strtoul(_audio_track_count, NULL, 10) : 0; udev_device_get_devnode(dev), vendor, model, optical, size);
LOG_DBG("device: add: %s: vendor=%s, model=%s, optical=%d, size=%" PRIu64, udev_device_get_devnode(dev), vendor,
model, optical, size);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_push_back(m->devices, ((struct block_device){.sys_path = strdup(udev_device_get_devpath(dev)), tll_push_back(
.dev_path = strdup(udev_device_get_devnode(dev)), m->devices,
.size = size, ((struct block_device){
.vendor = vendor != NULL ? strdup(vendor) : NULL, .sys_path = strdup(udev_device_get_devpath(dev)),
.model = model != NULL ? strdup(model) : NULL, .dev_path = strdup(udev_device_get_devnode(dev)),
.optical = optical, .size = size,
.media = media, .vendor = vendor != NULL ? strdup(vendor) : NULL,
.partitions = tll_init()})); .model = model != NULL ? strdup(model) : NULL,
.optical = optical,
.media = media,
.partitions = tll_init()}));
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
struct block_device *block = &tll_back(m->devices); struct block_device *block = &tll_back(m->devices);
if (optical) { if (optical && media)
if (have_fs) add_partition(mod, block, dev);
add_partition(mod, block, dev);
else if (audio_track_count > 0)
add_audio_cd(mod, block, dev);
}
return &tll_back(m->devices); return &tll_back(m->devices);
} }
@ -448,8 +386,7 @@ del_device(struct module *mod, struct udev_device *dev)
const char *sys_path = udev_device_get_devpath(dev); const char *sys_path = udev_device_get_devpath(dev);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
tll_foreach(m->devices, it) tll_foreach(m->devices, it) {
{
if (strcmp(it->item.sys_path, sys_path) == 0) { if (strcmp(it->item.sys_path, sys_path) == 0) {
LOG_DBG("device: del: %s", it->item.dev_path); LOG_DBG("device: del: %s", it->item.dev_path);
@ -471,51 +408,31 @@ change_device(struct module *mod, struct udev_device *dev)
const char *sys_path = udev_device_get_devpath(dev); const char *sys_path = udev_device_get_devpath(dev);
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
struct block_device *block = NULL; tll_foreach(m->devices, it) {
tll_foreach(m->devices, it)
{
if (strcmp(it->item.sys_path, sys_path) == 0) { if (strcmp(it->item.sys_path, sys_path) == 0) {
block = &it->item; LOG_DBG("device: change: %s", it->item.dev_path);
break;
if (it->item.optical) {
const char *_media = udev_device_get_property_value(dev, "ID_FS_USAGE");
bool media = _media != NULL && strcmp(_media, "filesystem") == 0;
bool media_change = media != it->item.media;
it->item.media = media;
mtx_unlock(&mod->lock);
if (media_change) {
LOG_INFO("device: change: %s: media %s",
it->item.dev_path, media ? "inserted" : "removed");
if (media)
return add_partition(mod, &it->item, dev) != NULL;
else
return del_partition(mod, &it->item, dev);
}
}
} }
} }
if (block == NULL)
goto out;
LOG_DBG("device: change: %s", block->dev_path);
if (!block->optical)
goto out;
const char *_media = udev_device_get_property_value(dev, "ID_CDROM_MEDIA");
bool media = _media != NULL && strcmp(_media, "1") == 0;
const char *_fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE");
bool have_fs = _fs_usage != NULL && strcmp(_fs_usage, "filesystem") == 0;
const char *_audio_track_count = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
unsigned long audio_track_count = _audio_track_count != NULL ? strtoul(_audio_track_count, NULL, 10) : 0;
bool media_change = media != block->media;
block->media = media;
mtx_unlock(&mod->lock);
if (media_change) {
LOG_INFO("device: change: %s: media %s", block->dev_path, media ? "inserted" : "removed");
if (media) {
if (have_fs)
return add_partition(mod, block, dev) != NULL;
else if (audio_track_count > 0)
return add_audio_cd(mod, block, dev) != NULL;
} else
return del_partition(mod, block, dev);
}
out:
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return false; return false;
} }
@ -550,8 +467,7 @@ handle_udev_event(struct module *mod, struct udev_device *dev)
struct udev_device *parent = udev_device_get_parent(dev); struct udev_device *parent = udev_device_get_parent(dev);
const char *parent_sys_path = udev_device_get_devpath(parent); const char *parent_sys_path = udev_device_get_devpath(parent);
tll_foreach(m->devices, it) tll_foreach(m->devices, it) {
{
if (strcmp(it->item.sys_path, parent_sys_path) != 0) if (strcmp(it->item.sys_path, parent_sys_path) != 0)
continue; continue;
@ -560,7 +476,8 @@ handle_udev_event(struct module *mod, struct udev_device *dev)
else if (del) else if (del)
return del_partition(mod, &it->item, dev); return del_partition(mod, &it->item, dev);
else { else {
LOG_ERR("unimplemented: 'change' event on partition: %s", udev_device_get_devpath(dev)); LOG_ERR("unimplemented: 'change' event on partition: %s",
udev_device_get_devpath(dev));
return false; return false;
} }
break; break;
@ -587,15 +504,15 @@ run(struct module *mod)
udev_enumerate_add_match_subsystem(dev_enum, "block"); udev_enumerate_add_match_subsystem(dev_enum, "block");
/* TODO: verify how an optical presents itself */ /* TODO: verify how an optical presents itself */
// udev_enumerate_add_match_sysattr(dev_enum, "removable", "1"); //udev_enumerate_add_match_sysattr(dev_enum, "removable", "1");
udev_enumerate_add_match_property(dev_enum, "DEVTYPE", "disk"); udev_enumerate_add_match_property(dev_enum, "DEVTYPE", "disk");
udev_enumerate_scan_devices(dev_enum); udev_enumerate_scan_devices(dev_enum);
/* Loop list, and for each device, enumerate its partitions */ /* Loop list, and for each device, enumerate its partitions */
struct udev_list_entry *entry = NULL; struct udev_list_entry *entry = NULL;
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(dev_enum)) udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(dev_enum)) {
{ struct udev_device *dev = udev_device_new_from_syspath(
struct udev_device *dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(entry)); udev, udev_list_entry_get_name(entry));
struct block_device *block = add_device(mod, dev); struct block_device *block = add_device(mod, dev);
if (block == NULL) { if (block == NULL) {
@ -612,9 +529,9 @@ run(struct module *mod)
udev_enumerate_scan_devices(part_enum); udev_enumerate_scan_devices(part_enum);
struct udev_list_entry *sub_entry = NULL; struct udev_list_entry *sub_entry = NULL;
udev_list_entry_foreach(sub_entry, udev_enumerate_get_list_entry(part_enum)) udev_list_entry_foreach(sub_entry, udev_enumerate_get_list_entry(part_enum)) {
{ struct udev_device *partition = udev_device_new_from_syspath(
struct udev_device *partition = udev_device_new_from_syspath(udev, udev_list_entry_get_name(sub_entry)); udev, udev_list_entry_get_name(sub_entry));
add_partition(mod, block, partition); add_partition(mod, block, partition);
udev_device_unref(partition); udev_device_unref(partition);
} }
@ -628,9 +545,7 @@ run(struct module *mod)
/* To be able to poll() mountinfo for changes, to detect /* To be able to poll() mountinfo for changes, to detect
* mount/unmount operations */ * mount/unmount operations */
int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY);
int ret = 1;
while (true) { while (true) {
struct pollfd fds[] = { struct pollfd fds[] = {
@ -638,26 +553,16 @@ run(struct module *mod)
{.fd = udev_monitor_get_fd(dev_mon), .events = POLLIN}, {.fd = udev_monitor_get_fd(dev_mon), .events = POLLIN},
{.fd = mount_info_fd, .events = POLLPRI}, {.fd = mount_info_fd, .events = POLLPRI},
}; };
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { poll(fds, 3, -1);
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll"); if (fds[0].revents & POLLIN)
break; break;
}
if (fds[0].revents & POLLIN) {
ret = 0;
break;
}
bool update = false; bool update = false;
if (fds[2].revents & POLLPRI) { if (fds[2].revents & POLLPRI) {
tll_foreach(m->devices, dev) tll_foreach(m->devices, dev) {
{ tll_foreach(dev->item.partitions, part) {
tll_foreach(dev->item.partitions, part)
{
if (update_mount_points(&part->item)) if (update_mount_points(&part->item))
update = true; update = true;
} }
@ -666,9 +571,6 @@ run(struct module *mod)
if (fds[1].revents & POLLIN) { if (fds[1].revents & POLLIN) {
struct udev_device *dev = udev_monitor_receive_device(dev_mon); struct udev_device *dev = udev_monitor_receive_device(dev_mon);
if (dev == NULL)
continue;
if (handle_udev_event(mod, dev)) if (handle_udev_event(mod, dev))
update = true; update = true;
udev_device_unref(dev); udev_device_unref(dev);
@ -682,12 +584,12 @@ run(struct module *mod)
udev_monitor_unref(dev_mon); udev_monitor_unref(dev_mon);
udev_unref(udev); udev_unref(udev);
return ret; return 0;
} }
static struct module * static struct module *
removables_new(struct particle *label, int left_spacing, int right_spacing, size_t ignore_count, removables_new(struct particle *label, int left_spacing, int right_spacing,
const char *ignore[static ignore_count]) size_t ignore_count, const char *ignore[static ignore_count])
{ {
struct private *priv = calloc(1, sizeof(*priv)); struct private *priv = calloc(1, sizeof(*priv));
priv->label = label; priv->label = label;
@ -715,22 +617,26 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); const struct yml_node *right_spacing = yml_get_value(node, "right-spacing");
const struct yml_node *ignore_list = yml_get_value(node, "ignore"); const struct yml_node *ignore_list = yml_get_value(node, "ignore");
int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; int left = spacing != NULL ? yml_value_as_int(spacing) :
int right = spacing != NULL ? yml_value_as_int(spacing) left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
: right_spacing != NULL ? yml_value_as_int(right_spacing) int right = spacing != NULL ? yml_value_as_int(spacing) :
: 0; right_spacing != NULL ? yml_value_as_int(right_spacing) : 0;
size_t ignore_count = ignore_list != NULL ? yml_list_length(ignore_list) : 0; size_t ignore_count = ignore_list != NULL ? yml_list_length(ignore_list) : 0;
const char *ignore[max(ignore_count, 1)]; const char *ignore[max(ignore_count, 1)];
if (ignore_list != NULL) { if (ignore_list != NULL) {
size_t i = 0; size_t i = 0;
for (struct yml_list_iter iter = yml_list_iter(ignore_list); iter.node != NULL; yml_list_next(&iter), i++) { for (struct yml_list_iter iter = yml_list_iter(ignore_list);
iter.node != NULL;
yml_list_next(&iter), i++)
{
ignore[i] = yml_value_as_string(iter.node); ignore[i] = yml_value_as_string(iter.node);
} }
} }
return removables_new(conf_to_particle(content, inherited), left, right, ignore_count, ignore); return removables_new(
conf_to_particle(content, inherited), left, right, ignore_count, ignore);
} }
static bool static bool
@ -743,9 +649,9 @@ static bool
verify_conf(keychain_t *chain, const struct yml_node *node) verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"spacing", false, &conf_verify_unsigned}, {"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_int},
{"ignore", false, &verify_ignore}, {"ignore", false, &verify_ignore},
MODULE_COMMON_ATTRS, MODULE_COMMON_ATTRS,
}; };

Some files were not shown because too many files have changed in this diff Show more