Compare commits

..

No commits in common. "master" and "releases/1.7" have entirely different histories.

124 changed files with 3446 additions and 11001 deletions

View file

@ -19,17 +19,13 @@ packages:
- json-c-dev
- libmpdclient-dev
- alsa-lib-dev
- pulseaudio-dev
- pipewire-dev
- ttf-dejavu
- gcovr
- python3
- py3-pip
- flex
- bison
sources:
- https://git.sr.ht/~dnkl/yambar
- https://codeberg.org/dnkl/yambar
# triggers:
# - action: email
@ -41,10 +37,6 @@ tasks:
pip install codespell
cd yambar
~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd
- fcft: |
cd yambar/subprojects
git clone https://codeberg.org/dnkl/fcft.git
cd ../..
- setup: |
mkdir -p bld/debug bld/release bld/x11-only bld/wayland-only bld/plugs-are-shared
meson --buildtype=debug -Db_coverage=true yambar bld/debug

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
* [Unreleased](#unreleased)
* [1.11.0](#1-11-0)
* [1.10.0](#1-10-0)
* [1.9.0](#1-9-0)
* [1.8.0](#1-8-0)
* [1.7.0](#1-7-0)
* [1.6.2](#1-6-2)
* [1.6.1](#1-6-1)
@ -12,441 +7,22 @@
* [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]).
[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
### Changed
* `river`: expand to an empty list of particles when river is not
running ([#384][384]).
[384]: https://codeberg.org/dnkl/yambar/issues/384
### Deprecated
### Removed
### Fixed
* network: fix missing break in switch statement ([#377][377]).
* i3/sway: crash when output is turned off an on ([#300][300]).
* mpd: yambar never attempting to reconnect after MPD closed the
connection (for example, when MPD is restarted).
* Bar positioning on multi-monitor setups, when `location=bottom`.
* pipewire: Improve handling of node switching ([#424][424]).
[377]: https://codeberg.org/dnkl/yambar/issues/377
[300]: https://codeberg.org/dnkl/yambar/issues/300
[424]: https://codeberg.org/dnkl/yambar/pulls/424
### Security
### Contributors
## 1.11.0
### Added
* battery: current smoothing, for improved discharge estimates.
* battery: scale option, for batteries that report 'charge' at a
different scale than 'current'.
* network: new `quality` tag (Wi-Fi only).
* Read alternative config from pipes and FIFOs (e.g. `--config
/dev/stdin`) ([#340][340]).
* Added `overlay` and `background` as possible `layer` values
([#372][372]).
[340]: https://codeberg.org/dnkl/yambar/pulls/340
[372]: https://codeberg.org/dnkl/yambar/issues/372
### Changed
* log-level: default to `warning`
* network: use dynlist instead of fixed name ([#355][355])
[355]: https://codeberg.org/dnkl/yambar/pulls/355
### Fixed
* Compiler error _fmt may be used uninitialized_ ([#311][311]).
* map: conditions failing to match when they contain multiple, quoted
tag values ([#302][302]).
* Crash when hidden by an opaque window.
* Bar not resizing itself when the screen resolution is changed
([#330][330]).
* i3/sway: incorrect empty/title state of workspaces ([#343][343]).
* mem: state updated on each bar redraw ([#352][352]).
* script: buffer overflow when reading large amounts of data.
* i3/sway: module fails when reloading config file ([#361][361]).
* Worked around bug in gcc causing a compilation error ([#350][350]).
* Miscalculation of list width in presence of empty particles ([#369][369]).
* Log-level not respected by syslog.
[311]: https://codeberg.org/dnkl/yambar/issues/311
[302]: https://codeberg.org/dnkl/yambar/issues/302
[330]: https://codeberg.org/dnkl/yambar/issues/330
[343]: https://codeberg.org/dnkl/yambar/issues/343
[352]: https://codeberg.org/dnkl/yambar/issues/352
[361]: https://codeberg.org/dnkl/yambar/issues/361
[350]: https://codeberg.org/dnkl/yambar/issues/350
[369]: https://codeberg.org/dnkl/yambar/issues/369
### Contributors
* Delgan
* Haden Collins
* Jordan Isaacs
* kotyk
* Leonardo Hernández Hernández
* oob
* rdbo
* Sertonix
* steovd
* Väinö Mäkelä
* Yiyu Zhou
## 1.10.0
### Added
* Field width tag format option ([#246][246])
* river: support for layout events.
* dwl: support for specifying name of tags ([#256][256])
* i3/sway: extend option `sort`; use `native` to sort numbered workspaces only.
* modules/dwl: handle the appid status ([#284][284])
* battery: also show estimation for time to full ([#303][303]).
* on-click: tilde expansion ([#307][307])
* script: tilde expansion of `path` ([#307][307]).
[246]: https://codeberg.org/dnkl/yambar/issues/246
[256]: https://codeberg.org/dnkl/yambar/pulls/256
[284]: https://codeberg.org/dnkl/yambar/pulls/284
[307]: https://codeberg.org/dnkl/yambar/issues/307
### Changed
* disk-io: `interval` renamed to `poll-interval`
* mem: `interval` renamed to `poll-interval`
* battery/network/script: `poll-interval` unit changed from seconds to
milliseconds ([#244][244]).
* all modules: minimum poll interval changed from 500ms to 250ms.
* network: do not use IPv6 link-local ([#281][281])
[244]: https://codeberg.org/dnkl/yambar/issues/244
[281]: https://codeberg.org/dnkl/yambar/pulls/281
### Fixed
* Build failures for certain combinations of enabled and disabled
plugins ([#239][239]).
* Documentation for the `cpu` module; `interval` has been renamed to
`poll-interval` ([#241][241]).
* battery: module was not thread safe.
* dwl module reporting only the last part of the title ([#251][251])
* i3/sway: regression; persistent workspaces shown twice
([#253][253]).
* pipewire: use `roundf()` instead of `ceilf()` for more accuracy
([#262][262])
* Crash when a yaml anchor has a value that already exists in the
target yaml node ([#286][286]).
* battery: Fix time conversion in battery estimation ([#303][303]).
* battery: poll timeout being reset when receiving irrelevant udev
notification (leading to battery status never updating, in worst
case) ([#305][305]).
[239]: https://codeberg.org/dnkl/yambar/issues/239
[241]: https://codeberg.org/dnkl/yambar/issues/241
[251]: https://codeberg.org/dnkl/yambar/pulls/251
[253]: https://codeberg.org/dnkl/yambar/issues/253
[262]: https://codeberg.org/dnkl/yambar/issues/262
[286]: https://codeberg.org/dnkl/yambar/issues/286
[305]: https://codeberg.org/dnkl/yambar/issues/305
### Contributors
* Leonardo Gibrowski Faé (Horus)
* Armin Fisslthaler
* Ben Brown
* David Bimmler
* Leonardo Hernández Hernández
* Ogromny
* Oleg Hahm
* Stanislav Ochotnický
* tiosgz
* Yutaro Ohno
## 1.9.0
### Added
* Support for specifying number of decimals when printing a float tag
([#200][200]).
* Support for custom font fallbacks ([#153][153]).
* overline: new decoration ([#153][153]).
* i3/sway: boolean option `strip-workspace-numbers`.
* font-shaping: new inheritable configuration option, allowing you to
configure whether strings should be _shaped_ using HarfBuzz, or not
([#159][159]).
* river: support for the new “mode” event present in version 3 of the
river status manager protocol, in the form of a new tag, _”mode”_,
in the `title` particle.
* network: request link stats and expose under tags `dl-speed` and
`ul-speed` when `poll-interval` is set.
* new module: disk-io.
* new module: pulse ([#223][223]).
* alsa: `dB` tag ([#202][202]).
* mpd: `file` tag ([#219][219]).
* pipewire: add a new module for pipewire ([#224][224])
* on-click: support `next`/`previous` mouse buttons ([#228][228]).
* dwl: add a new module for DWL ([#218][218])
* sway: support for workspace rename and move events
([#216][216]).
[153]: https://codeberg.org/dnkl/yambar/issues/153
[159]: https://codeberg.org/dnkl/yambar/issues/159
[200]: https://codeberg.org/dnkl/yambar/issues/200
[202]: https://codeberg.org/dnkl/yambar/issues/202
[218]: https://codeberg.org/dnkl/yambar/pulls/218
[219]: https://codeberg.org/dnkl/yambar/pulls/219
[223]: https://codeberg.org/dnkl/yambar/pulls/223
[224]: https://codeberg.org/dnkl/yambar/pulls/224
[228]: https://codeberg.org/dnkl/yambar/pulls/228
[216]: https://codeberg.org/dnkl/yambar/issues/216
### Changed
* All modules are now compile-time optional.
* Minimum required meson version is now 0.59.
* Float tags are now treated as floats instead of integers when
formatted with the `kb`/`kib`/`mb`/`mib`/`gb`/`gib` string particle
formatters.
* network: `tx-bitrate` and `rx-bitrate` are now in bits/s instead of
Mb/s. Use the `mb` string formatter to render these tags as before
(e.g. `string: {text: "{tx-bitrate:mb}"}`).
* i3: newly created, and **unfocused** workspaces are now considered
non-empty ([#191][191])
* alsa: use dB instead of raw volume values, if possible, when
calculating the `percent` tag ([#202][202])
* cpu: `content` particle is now a template instantiated once for each
core, and once for the total CPU usage. See
**yambar-modules-cpu**(5) for more information ([#207][207]).
* **BREAKING CHANGE**: overhaul of the `map` particle. Instead of
specifying a `tag` and then an array of `values`, you must now
simply use an array of `conditions`, that consist of:
`<tag> <operation> <value>`
where `<operation>` is one of:
`== != < <= > >=`
Note that boolean tags must be used as is:
`online`
`~online # use '~' to match for their falsehood`
As an example, if you previously had something like:
```
map:
tag: State
values:
unrecognized:
...
```
You would now write it as:
```
map:
conditions:
State == unrecognized:
...
```
Note that if `<value>` contains any non-alphanumerical characters,
it **must** be surrounded by `""`:
`State == "very confused!!!"`
Finally, you can mix and match conditions using the boolean
operators `&&` and `||`:
```
<condition1> && <condition2>
<condition1> && (<condition2> || <condition3>) # parenthesis work
~(<condition1> && <condition2>) # '~' can be applied to any condition
```
For a more thorough explanation, see the updated map section in the
man page for yambar-particles([#137][137], [#175][175] and [#][182]).
[137]: https://codeberg.org/dnkl/yambar/issues/137
[175]: https://codeberg.org/dnkl/yambar/issues/172
[182]: https://codeberg.org/dnkl/yambar/issues/182
[191]: https://codeberg.org/dnkl/yambar/issues/191
[202]: https://codeberg.org/dnkl/yambar/issues/202
[207]: https://codeberg.org/dnkl/yambar/issues/207
### Fixed
* i3: fixed “missing workspace indicator” (_err: modules/i3.c:94:
workspace reply/event without 'name' and/or 'output', and/or 'focus'
properties_).
* Slow/laggy behavior when quickly spawning many `on-click` handlers,
e.g. when handling mouse wheel events ([#169][169]).
* cpu: dont error out on systems where SMT has been disabled
([#172][172]).
* examples/dwl-tags: updated parsing of `output` name ([#178][178]).
* sway-xkb: dont crash when Sway sends an _”added”_ event for a
device yambar is already tracking ([#177][177]).
* Crash when a particle is “too wide”, and tries to render outside the
bar ([#198][198]).
* string: crash when failing to convert string to UTF-32.
* script: only first transaction processed when receiving multiple
transactions in a single batch ([#221][221]).
* network: missing SSID (recent kernels, or possibly wireless drivers,
no longer provide the SSID in the `NL80211_CMD_NEW_STATION`
response) ([#226][226]).
* sway-xkb: crash when compositor presents multiple inputs with
identical IDs ([#229][229]).
[169]: https://codeberg.org/dnkl/yambar/issues/169
[172]: https://codeberg.org/dnkl/yambar/issues/172
[178]: https://codeberg.org/dnkl/yambar/issues/178
[177]: https://codeberg.org/dnkl/yambar/issues/177
[198]: https://codeberg.org/dnkl/yambar/issues/198
[221]: https://codeberg.org/dnkl/yambar/issues/221
[226]: https://codeberg.org/dnkl/yambar/issues/226
[229]: https://codeberg.org/dnkl/yambar/issues/229
### Contributors
* Baptiste Daroussin
* Horus
* Johannes
* Leonardo Gibrowski Faé
* Leonardo Neumann
* Midgard
* Ogromny
* Peter Rice
* Timur Celik
* Willem van de Krol
* hiog
## 1.8.0
### Added
* ramp: can now have custom min and max values
([#103](https://codeberg.org/dnkl/yambar/issues/103)).
* border: new decoration.
* i3/sway: new boolean tag: `empty`
([#139](https://codeberg.org/dnkl/yambar/issues/139)).
* mem: a module handling system memory monitoring
* cpu: a module offering cpu usage monitoring
* removables: support for audio CDs
([#146](https://codeberg.org/dnkl/yambar/issues/146)).
* removables: new boolean tag: `audio`.
### Changed
* fcft >= 3.0 is now required.
* Made `libmpdclient` an optional dependency
* battery: unknown battery states are now mapped to unknown, instead
of discharging.
* Wayland: the bar no longer exits when the monitor is
disabled/unplugged ([#106](https://codeberg.org/dnkl/yambar/issues/106)).
### Fixed
* `left-margin` and `right-margin` from being rejected as invalid
options.
* Crash when `udev_monitor_receive_device()` returned `NULL`. This
affected the “backlight”, “battery” and “removables” modules
([#109](https://codeberg.org/dnkl/yambar/issues/109)).
* foreign-toplevel: update bar when a top-level is closed.
* Bar not being mapped on an output before at least one module has
“refreshed” it ([#116](https://codeberg.org/dnkl/yambar/issues/116)).
* network: failure to retrieve wireless attributes (SSID, RX/TX
bitrate, signal strength etc).
* Integer options that were supposed to be >= 0 were incorrectly
allowed, leading to various bad things; including yambar crashing,
or worse, the compositor crashing
([#129](https://codeberg.org/dnkl/yambar/issues/129)).
* kib/kb, mib/mb and gib/gb formatters were inverted.
### Contributors
* [sochotnicky](https://codeberg.org/sochotnicky)
* Alexandre Acebedo
* anb
* Baptiste Daroussin
* Catterwocky
* horus645
* Jan Beich
* mz
* natemaia
* nogerine
* Soc Virnyl S. Estela
* Vincent Fischer
## 1.7.0
### Added
* i3: `persistent` attribute, allowing persistent workspaces
([#72](https://codeberg.org/dnkl/yambar/issues/72)).
(https://codeberg.org/dnkl/yambar/issues/72).
* bar: `border.{left,right,top,bottom}-width`, allowing the width of
each side of the border to be configured
individually. `border.width` is now a short-hand for setting all
four borders to the same value
([#77](https://codeberg.org/dnkl/yambar/issues/77)).
(https://codeberg.org/dnkl/yambar/issues/77).
* bar: `layer: top|bottom`, allowing the layer which the bar is
rendered on to be changed. Wayland only - ignored on X11.
* river: `all-monitors: false|true`.
* `-d,--log-level=info|warning|error|none` command line option
([#84](https://codeberg.org/dnkl/yambar/issues/84)).
(https://codeberg.org/dnkl/yambar/issues/84).
* river: support for the river-status protocol, version 2 (urgent
views).
* `online` tag to the `alsa` module.
@ -458,16 +34,17 @@
* network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags.
* network: `poll-interval` option (for the new `signal` and
`*-bitrate` tags).
* tags: percentage 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.
* clock: add a config option to show UTC time.
### Changed
* bar: do not add `spacing` around empty (zero-width) modules.
* alsa: do not error out if we fail to connect to the ALSA device, or
if we get disconnected. Instead, keep retrying until we succeed
([#86](https://codeberg.org/dnkl/yambar/issues/86)).
(https://codeberg.org/dnkl/yambar/issues/86).
### Fixed
@ -477,7 +54,7 @@
* Regression: `{where}` tag not being expanded in progress-bar
`on-click` handlers.
* `alsa` module causing yambar to use 100% CPU if the ALSA device is
disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)).
disconnected (https://codeberg.org/dnkl/yambar/issues/61).
### Contributors
@ -493,39 +70,39 @@
* Text shaping support.
* Support for middle and right mouse buttons, mouse wheel and trackpad
scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)).
scrolling (https://codeberg.org/dnkl/yambar/issues/39).
* script: polling mode. See the new `poll-interval` option
([#67](https://codeberg.org/dnkl/yambar/issues/67)).
(https://codeberg.org/dnkl/yambar/issues/67).
### Changed
* doc: split up **yambar-modules**(5) into multiple man pages, one for
each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)).
each module (https://codeberg.org/dnkl/yambar/issues/15).
* fcft >= 2.4.0 is now required.
* sway-xkb: non-keyboard inputs are now ignored
([#51](https://codeberg.org/dnkl/yambar/issues/51)).
(https://codeberg.org/dnkl/yambar/issues/51).
* battery: dont terminate (causing last status to “freeze”) when
failing to update; retry again later
([#44](https://codeberg.org/dnkl/yambar/issues/44)).
(https://codeberg.org/dnkl/yambar/issues/44).
* battery: differentiate "Not Charging" and "Discharging" in state
tag of battery module.
([#57](https://codeberg.org/dnkl/yambar/issues/57)).
(https://codeberg.org/dnkl/yambar/issues/57).
* string: use HORIZONTAL ELLIPSIS instead of three regular periods
when truncating a string
([#73](https://codeberg.org/dnkl/yambar/issues/73)).
(https://codeberg.org/dnkl/yambar/issues/73).
### Fixed
* Crash when merging non-dictionary anchors in the YAML configuration
([#32](https://codeberg.org/dnkl/yambar/issues/32)).
(https://codeberg.org/dnkl/yambar/issues/32).
* Crash in the `ramp` particle when the tags value was out-of-bounds
([#45](https://codeberg.org/dnkl/yambar/issues/45)).
(https://codeberg.org/dnkl/yambar/issues/45).
* Crash when a string particle contained `{}`
([#48](https://codeberg.org/dnkl/yambar/issues/48)).
(https://codeberg.org/dnkl/yambar/issues/48).
* `script` module rejecting range tag end values containing the digit
`9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)).
`9` (https://codeberg.org/dnkl/yambar/issues/60).
### Contributors
@ -540,7 +117,7 @@
* i3: workspaces with numerical names are sorted separately from
non-numerically named workspaces
([#30](https://codeberg.org/dnkl/yambar/issues/30)).
(https://codeberg.org/dnkl/yambar/issues/30).
### Fixed
@ -548,7 +125,7 @@
* mpd: `elapsed` tag not working (regression, introduced in 1.6.0).
* Wrong background color for (semi-) transparent backgrounds.
* battery: stats sometimes getting stuck at 0, or impossibly large
values ([#25](https://codeberg.org/dnkl/yambar/issues/25)).
values (https://codeberg.org/dnkl/yambar/issues/25).
## 1.6.0
@ -557,17 +134,17 @@
* alsa: `percent` tag. This is an integer tag that represents the
current volume as a percentage value
([#10](https://codeberg.org/dnkl/yambar/issues/10)).
(https://codeberg.org/dnkl/yambar/issues/10).
* river: added documentation
([#9](https://codeberg.org/dnkl/yambar/issues/9)).
(https://codeberg.org/dnkl/yambar/issues/9).
* script: new module, adds support for custom user scripts
([#11](https://codeberg.org/dnkl/yambar/issues/11)).
(https://codeberg.org/dnkl/yambar/issues/11).
* mpd: `volume` tag. This is a range tag that represents MPD's current
volume in percentage (0-100)
* i3: `sort` configuration option, that controls how the workspace
list is sorted. Can be set to one of `none`, `ascending` or
`descending`. Default is `none`
([#17](https://codeberg.org/dnkl/yambar/issues/17)).
(https://codeberg.org/dnkl/yambar/issues/17).
* i3: `mode` tag: the name of the currently active mode
@ -577,12 +154,12 @@
error”_.
* Memory leak when a YAML parsing error was encountered.
* clock: update every second when necessary
([#12](https://codeberg.org/dnkl/yambar/issues/12)).
(https://codeberg.org/dnkl/yambar/issues/12).
* mpd: fix compilation with clang
([#16](https://codeberg.org/dnkl/yambar/issues/16)).
(https://codeberg.org/dnkl/yambar/issues/16).
* Crash when the alpha component in a color value was 0.
* XCB: Fallback to non-primary monitor when the primary monitor is
disconnected ([#20](https://codeberg.org/dnkl/yambar/issues/20))
disconnected (https://codeberg.org/dnkl/yambar/issues/20)
### Contributors

View file

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

View file

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

View file

@ -1,8 +1,6 @@
[![CI status](https://ci.codeberg.org/api/badges/dnkl/yambar/status.svg)](https://ci.codeberg.org/dnkl/yambar)
# Yambar
[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg?columns=4)](https://repology.org/project/yambar/versions)
[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg)](https://repology.org/project/yambar/versions)
## Index
@ -59,9 +57,9 @@ bar:
right:
- clock:
content:
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
- string: {text: "{date}", right-margin: 5}
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: , font: "Font Awesome 5 Free:style=solid:size=12"}
- string: {text: "{time}"}
```
@ -78,17 +76,10 @@ Available modules:
* backlight
* battery
* clock
* cpu
* disk-io
* dwl
* foreign-toplevel
* i3 (and Sway)
* label
* mem
* mpd
* network
* pipewire
* pulse
* removables
* river
* script (see script [examples](examples/scripts))
@ -107,7 +98,7 @@ mkdir -p bld/release && cd bld/release
Second, configure the build (if you intend to install it globally, you
might also want `--prefix=/usr`):
```sh
meson setup --buildtype=release ../..
meson --buildtype=release ../..
```
Optionally, explicitly disable a backend (or enable, if you want a

View file

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

View file

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

View file

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

View file

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

View file

@ -3,8 +3,7 @@
#include "../bar/bar.h"
#include "backend.h"
struct private
{
struct private {
/* From bar_config */
char *monitor;
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 <assert.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <poll.h>
#include <pthread.h>
#include <unistd.h>
#include <pixman.h>
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_cursor.h>
#include <xcb/xcb_event.h>
@ -39,6 +39,7 @@ struct xcb_backend {
void *client_pixmap;
size_t client_pixmap_size;
pixman_image_t *pix;
};
void *
@ -54,8 +55,11 @@ setup(struct bar *_bar)
struct private *bar = _bar->private;
struct xcb_backend *backend = bar->backend.data;
if (bar->border.left_margin != 0 || bar->border.right_margin != 0 || bar->border.top_margin != 0
|| bar->border.bottom_margin) {
if (bar->border.left_margin != 0 ||
bar->border.right_margin != 0 ||
bar->border.top_margin != 0 ||
bar->border.bottom_margin)
{
LOG_WARN("non-zero border margins ignored in X11 backend");
}
@ -72,8 +76,10 @@ setup(struct bar *_bar)
xcb_screen_t *screen = xcb_aux_get_screen(backend->conn, default_screen);
xcb_randr_get_monitors_reply_t *monitors
= xcb_randr_get_monitors_reply(backend->conn, xcb_randr_get_monitors(backend->conn, screen->root, 0), &e);
xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply(
backend->conn,
xcb_randr_get_monitors(backend->conn, screen->root, 0),
&e);
if (e != NULL) {
LOG_ERR("failed to get monitor list: %s", xcb_error(e));
@ -84,13 +90,17 @@ setup(struct bar *_bar)
/* Find monitor coordinates and width/height */
bool found_monitor = false;
for (xcb_randr_monitor_info_iterator_t it = xcb_randr_get_monitors_monitors_iterator(monitors); it.rem > 0;
xcb_randr_monitor_info_next(&it)) {
for (xcb_randr_monitor_info_iterator_t it =
xcb_randr_get_monitors_monitors_iterator(monitors);
it.rem > 0;
xcb_randr_monitor_info_next(&it))
{
const xcb_randr_monitor_info_t *mon = it.data;
char *name = get_atom_name(backend->conn, mon->name);
LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name, mon->width, mon->height, mon->x, mon->y,
mon->width_in_millimeters, mon->height_in_millimeters);
LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name,
mon->width, mon->height, mon->x, mon->y,
mon->width_in_millimeters, mon->height_in_millimeters);
/* User wants a specific monitor, and this is not the one */
if (bar->monitor != NULL && strcmp(bar->monitor, name) != 0) {
@ -101,11 +111,14 @@ setup(struct bar *_bar)
backend->x = mon->x;
backend->y = mon->y;
bar->width = mon->width;
backend->y += bar->location == BAR_TOP ? 0 : mon->height - bar->height_with_border;
backend->y += bar->location == BAR_TOP ? 0
: screen->height_in_pixels - bar->height_with_border;
found_monitor = true;
if ((bar->monitor != NULL && strcmp(bar->monitor, name) == 0) || (bar->monitor == NULL && mon->primary)) {
if ((bar->monitor != NULL && strcmp(bar->monitor, name) == 0) ||
(bar->monitor == NULL && mon->primary))
{
/* Exact match */
free(name);
break;
@ -142,47 +155,74 @@ setup(struct bar *_bar)
LOG_DBG("using a %hhu-bit visual", depth);
backend->colormap = xcb_generate_id(backend->conn);
xcb_create_colormap(backend->conn, 0, backend->colormap, screen->root, vis->visual_id);
xcb_create_colormap(
backend->conn, 0, backend->colormap, screen->root, vis->visual_id);
backend->win = xcb_generate_id(backend->conn);
xcb_create_window(
backend->conn, depth, backend->win, screen->root, backend->x, backend->y, bar->width, bar->height_with_border,
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
(XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP),
(const uint32_t[]){screen->black_pixel, screen->white_pixel,
(XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_STRUCTURE_NOTIFY),
backend->colormap});
backend->conn,
depth, backend->win, screen->root,
backend->x, backend->y, bar->width, bar->height_with_border,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
(XCB_CW_BACK_PIXEL |
XCB_CW_BORDER_PIXEL |
XCB_CW_EVENT_MASK |
XCB_CW_COLORMAP),
(const uint32_t []){
screen->black_pixel,
screen->white_pixel,
(XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_STRUCTURE_NOTIFY),
backend->colormap}
);
const char *title = "yambar";
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
strlen(title), title);
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
strlen(title), title);
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1,
(const uint32_t[]){getpid()});
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 1,
(const uint32_t[]){_NET_WM_WINDOW_TYPE_DOCK});
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STATE, XCB_ATOM_ATOM, 32, 2,
(const uint32_t[]){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1,
(const uint32_t[]){0xffffffff});
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){getpid()});
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
_NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32,
1, (const uint32_t []){_NET_WM_WINDOW_TYPE_DOCK});
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
_NET_WM_STATE, XCB_ATOM_ATOM, 32,
2, (const uint32_t []){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
_NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){0xffffffff});
/* Always on top */
xcb_configure_window(backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE,
(const uint32_t[]){XCB_STACK_MODE_ABOVE});
xcb_configure_window(
backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE,
(const uint32_t []){XCB_STACK_MODE_ABOVE});
uint32_t top_strut, bottom_strut;
uint32_t top_pair[2], bottom_pair[2];
if (bar->location == BAR_TOP) {
top_strut = bar->height_with_border;
top_strut = backend->y + bar->height_with_border;
top_pair[0] = backend->x;
top_pair[1] = backend->x + bar->width - 1;
bottom_strut = 0;
bottom_pair[0] = bottom_pair[1] = 0;
} else {
bottom_strut = bar->height_with_border;
bottom_strut = screen->height_in_pixels - backend->y;
bottom_pair[0] = backend->x;
bottom_pair[1] = backend->x + bar->width - 1;
@ -192,38 +232,42 @@ setup(struct bar *_bar)
uint32_t strut[] = {
/* left/right/top/bottom */
0,
0,
0, 0,
top_strut,
bottom_strut,
/* start/end pairs for left/right/top/bottom */
0,
0,
0,
0,
top_pair[0],
top_pair[1],
bottom_pair[0],
bottom_pair[1],
0, 0,
0, 0,
top_pair[0], top_pair[1],
bottom_pair[0], bottom_pair[1],
};
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4,
strut);
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
_NET_WM_STRUT, XCB_ATOM_CARDINAL, 32,
4, strut);
xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL,
32, 12, strut);
xcb_change_property(
backend->conn,
XCB_PROP_MODE_REPLACE, backend->win,
_NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32,
12, strut);
backend->gc = xcb_generate_id(backend->conn);
xcb_create_gc(backend->conn, backend->gc, backend->win, XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
(const uint32_t[]){screen->white_pixel, 0});
xcb_create_gc(backend->conn, backend->gc, backend->win,
XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
(const uint32_t []){screen->white_pixel, 0});
const uint32_t stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, bar->width);
const uint32_t stride = stride_for_format_and_width(
PIXMAN_a8r8g8b8, bar->width);
backend->client_pixmap_size = stride * bar->height_with_border;
backend->client_pixmap = malloc(backend->client_pixmap_size);
backend->pix = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
(uint32_t *)backend->client_pixmap, stride);
backend->pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, bar->width, bar->height_with_border,
(uint32_t *)backend->client_pixmap, stride);
bar->pix = backend->pix;
xcb_map_window(backend->conn, backend->win);
@ -266,8 +310,10 @@ cleanup(struct bar *_bar)
}
static void
loop(struct bar *_bar, void (*expose)(const struct bar *bar),
void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y))
loop(struct bar *_bar,
void (*expose)(const struct bar *bar),
void (*on_mouse)(struct bar *bar, enum mouse_event event,
enum mouse_button btn, int x, int y))
{
struct private *bar = _bar->private;
struct xcb_backend *backend = bar->backend.data;
@ -277,7 +323,10 @@ loop(struct bar *_bar, void (*expose)(const struct bar *bar),
const int fd = xcb_get_file_descriptor(backend->conn);
while (true) {
struct pollfd fds[] = {{.fd = _bar->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}};
struct pollfd fds[] = {
{.fd = _bar->abort_fd, .events = POLLIN},
{.fd = fd, .events = POLLIN}
};
poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
@ -286,14 +335,18 @@ loop(struct bar *_bar, void (*expose)(const struct bar *bar),
if (fds[1].revents & POLLHUP) {
LOG_WARN("disconnected from XCB");
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
!= sizeof(uint64_t))
{
LOG_ERRNO("failed to signal abort to modules");
}
break;
}
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn); e != NULL;
e = xcb_poll_for_event(backend->conn)) {
for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn);
e != NULL;
e = xcb_poll_for_event(backend->conn))
{
switch (XCB_EVENT_RESPONSE_TYPE(e)) {
case 0:
LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e));
@ -316,12 +369,9 @@ loop(struct bar *_bar, void (*expose)(const struct bar *bar),
const xcb_button_release_event_t *evt = (void *)e;
switch (evt->detail) {
case 1:
case 2:
case 3:
case 4:
case 5:
on_mouse(_bar, ON_MOUSE_CLICK, evt->detail, evt->event_x, evt->event_y);
case 1: case 2: case 3: case 4: case 5:
on_mouse(_bar, ON_MOUSE_CLICK,
evt->detail, evt->event_x, evt->event_y);
break;
}
break;
@ -355,9 +405,10 @@ commit(const struct bar *_bar)
const struct private *bar = _bar->private;
const struct xcb_backend *backend = bar->backend.data;
xcb_put_image(backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc, bar->width,
bar->height_with_border, 0, 0, 0, backend->depth, backend->client_pixmap_size,
backend->client_pixmap);
xcb_put_image(
backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc,
bar->width, bar->height_with_border, 0, 0, 0,
backend->depth, backend->client_pixmap_size, backend->client_pixmap);
xcb_flush(backend->conn);
}
@ -369,19 +420,23 @@ refresh(const struct bar *_bar)
/* Send an event to handle refresh from main thread */
/* Note: docs say that all X11 events are 32 bytes, regardless of
/* Note: docs say that all X11 events are 32 bytes, reglardless of
* the size of the event structure */
xcb_expose_event_t *evt = calloc(32, 1);
*evt = (xcb_expose_event_t){.response_type = XCB_EXPOSE,
.window = backend->win,
.x = 0,
.y = 0,
.width = bar->width,
.height = bar->height,
.count = 1};
*evt = (xcb_expose_event_t){
.response_type = XCB_EXPOSE,
.window = backend->win,
.x = 0,
.y = 0,
.width = bar->width,
.height = bar->height,
.count = 1
};
xcb_send_event(backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE, (char *)evt);
xcb_send_event(
backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE,
(char *)evt);
xcb_flush(backend->conn);
free(evt);
@ -403,7 +458,8 @@ set_cursor(struct bar *_bar, const char *cursor)
xcb_free_cursor(backend->conn, backend->cursor);
backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor);
xcb_change_window_attributes(backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
xcb_change_window_attributes(
backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor);
}
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 --validate)'{-C,--validate}'[verify configuration then quit]' \
'(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running]:pidfile:_files' \
'(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \
'(-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)' \
'(-s --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging]'

View file

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

View file

@ -26,14 +26,16 @@ chain_pop(keychain_t *chain)
tll_pop_back(*chain);
}
const char *conf_err_prefix(const keychain_t *chain, const struct yml_node *node);
const char *conf_err_prefix(
const keychain_t *chain, const struct yml_node *node);
bool conf_verify_string(keychain_t *chain, const struct yml_node *node);
bool conf_verify_int(keychain_t *chain, const struct yml_node *node);
bool conf_verify_unsigned(keychain_t *chain, const struct yml_node *node);
bool conf_verify_bool(keychain_t *chain, const struct yml_node *node);
bool conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count);
bool conf_verify_enum(keychain_t *chain, const struct yml_node *node,
const char *values[], size_t count);
bool conf_verify_list(keychain_t *chain, const struct yml_node *node,
bool (*verify)(keychain_t *chain, const struct yml_node *node));
bool conf_verify_dict(keychain_t *chain, const struct yml_node *node,
@ -42,7 +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_color(keychain_t *chain, const struct yml_node *node);
bool conf_verify_font(keychain_t *chain, const struct yml_node *node);
bool conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node);
bool conf_verify_particle(keychain_t *chain, const struct yml_node *node);
bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node);

216
config.c
View file

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

View file

@ -1,9 +1,8 @@
#pragma once
#include "bar/bar.h"
#include "font-shaping.h"
#include "yml.h"
#include <fcft/fcft.h>
#include "yml.h"
#include "bar/bar.h"
struct bar;
struct particle;
@ -17,13 +16,12 @@ struct bar *conf_to_bar(const struct yml_node *bar, enum bar_backend backend);
pixman_color_t conf_to_color(const struct yml_node *node);
struct fcft_font *conf_to_font(const struct yml_node *node);
enum font_shaping conf_to_font_shaping(const struct yml_node *node);
struct conf_inherit {
const struct fcft_font *font;
enum font_shaping font_shaping;
pixman_color_t foreground;
};
struct particle *conf_to_particle(const struct yml_node *node, struct conf_inherit inherited);
struct particle *conf_to_particle(
const struct yml_node *node, struct conf_inherit inherited);
struct deco *conf_to_deco(const struct yml_node *node);

View file

@ -4,11 +4,10 @@
struct deco {
void *private;
void (*expose)(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height);
void (*expose)(const struct deco *deco, pixman_image_t *pix,
int x, int y, int width, int height);
void (*destroy)(struct deco *deco);
};
#define DECORATION_COMMON_ATTRS \
{ \
NULL, false, NULL \
}
#define DECORATION_COMMON_ATTRS \
{NULL, false, NULL}

View file

@ -1,13 +1,12 @@
#include <stdlib.h>
#include "../config-verify.h"
#include "../config.h"
#include "../config-verify.h"
#include "../decoration.h"
#include "../plugin.h"
struct private
{
// struct rgba color;
struct private {
//struct rgba color;
pixman_color_t color;
};
@ -23,7 +22,9 @@ static void
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
{
const struct private *d = deco->private;
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, height});
pixman_image_fill_rectangles(
PIXMAN_OP_OVER, pix, &d->color, 1,
&(pixman_rectangle16_t){x, y, width, height});
}
static struct deco *

View file

@ -1,91 +0,0 @@
#include <stdlib.h>
#include "../config-verify.h"
#include "../config.h"
#include "../decoration.h"
#include "../plugin.h"
#define LOG_MODULE "border"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))
struct private
{
pixman_color_t color;
int size;
};
static void
destroy(struct deco *deco)
{
struct private *d = deco->private;
free(d);
free(deco);
}
static void
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
{
const struct private *d = deco->private;
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 4,
(pixman_rectangle16_t[]){
/* Top */
{x, y, width, min(d->size, height)},
/* Bottom */
{x, max(y + height - d->size, y), width, min(d->size, height)},
/* Left */
{x, y, min(d->size, width), height},
/* Right */
{max(x + width - d->size, x), y, min(d->size, width), height},
});
}
static struct deco *
border_new(pixman_color_t color, int size)
{
struct private *priv = calloc(1, sizeof(*priv));
priv->color = color;
priv->size = size;
struct deco *deco = calloc(1, sizeof(*deco));
deco->private = priv;
deco->expose = &expose;
deco->destroy = &destroy;
return deco;
}
static struct deco *
from_conf(const struct yml_node *node)
{
const struct yml_node *color = yml_get_value(node, "color");
const struct yml_node *size = yml_get_value(node, "size");
return border_new(conf_to_color(color), size != NULL ? yml_value_as_int(size) : 1);
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"color", true, &conf_verify_color},
{"size", false, &conf_verify_unsigned},
DECORATION_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct deco_iface deco_border_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct deco_iface iface __attribute__((weak, alias("deco_border_iface")));
#endif

View file

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

View file

@ -1,71 +0,0 @@
#include <stdlib.h>
#include "../config-verify.h"
#include "../config.h"
#include "../decoration.h"
#include "../plugin.h"
struct private
{
int size;
pixman_color_t color;
};
static void
destroy(struct deco *deco)
{
struct private *d = deco->private;
free(d);
free(deco);
}
static void
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
{
const struct private *d = deco->private;
pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, d->size});
}
static struct deco *
overline_new(int size, pixman_color_t color)
{
struct private *priv = calloc(1, sizeof(*priv));
priv->size = size;
priv->color = color;
struct deco *deco = calloc(1, sizeof(*deco));
deco->private = priv;
deco->expose = &expose;
deco->destroy = &destroy;
return deco;
}
static struct deco *
from_conf(const struct yml_node *node)
{
const struct yml_node *size = yml_get_value(node, "size");
const struct yml_node *color = yml_get_value(node, "color");
return overline_new(yml_value_as_int(size), conf_to_color(color));
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"size", true, &conf_verify_unsigned},
{"color", true, &conf_verify_color},
DECORATION_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct deco_iface deco_overline_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct deco_iface iface __attribute__((weak, alias("deco_overline_iface")));
#endif

View file

@ -1,14 +1,13 @@
#include <stdlib.h>
#define LOG_MODULE "stack"
#include "../config-verify.h"
#include "../config.h"
#include "../decoration.h"
#include "../log.h"
#include "../config.h"
#include "../config-verify.h"
#include "../decoration.h"
#include "../plugin.h"
struct private
{
struct private {
struct deco **decos;
size_t count;
};
@ -58,7 +57,10 @@ from_conf(const struct yml_node *node)
struct deco *decos[count];
size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) {
for (struct yml_list_iter it = yml_list_iter(node);
it.node != NULL;
yml_list_next(&it), idx++)
{
decos[idx] = conf_to_deco(it.node);
}
@ -73,7 +75,10 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
return false;
}
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
for (struct yml_list_iter it = yml_list_iter(node);
it.node != NULL;
yml_list_next(&it))
{
if (!conf_verify_decoration(chain, it.node))
return false;
}

View file

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

View file

@ -1,83 +1,19 @@
sh = find_program('sh', native: true)
scdoc = dependency('scdoc', native: true)
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
plugin_pages = []
if plugin_alsa_enabled
plugin_pages += ['yambar-modules-alsa.5.scd']
endif
if plugin_backlight_enabled
plugin_pages += ['yambar-modules-backlight.5.scd']
endif
if plugin_battery_enabled
plugin_pages += ['yambar-modules-battery.5.scd']
endif
if plugin_clock_enabled
plugin_pages += ['yambar-modules-clock.5.scd']
endif
if plugin_cpu_enabled
plugin_pages += ['yambar-modules-cpu.5.scd']
endif
if plugin_disk_io_enabled
plugin_pages += ['yambar-modules-disk-io.5.scd']
endif
if plugin_dwl_enabled
plugin_pages += ['yambar-modules-dwl.5.scd']
endif
if plugin_foreign_toplevel_enabled
plugin_pages += ['yambar-modules-foreign-toplevel.5.scd']
endif
if plugin_mem_enabled
plugin_pages += ['yambar-modules-mem.5.scd']
endif
if plugin_mpd_enabled
plugin_pages += ['yambar-modules-mpd.5.scd']
endif
if plugin_i3_enabled
plugin_pages += ['yambar-modules-i3.5.scd']
plugin_pages += ['yambar-modules-sway.5.scd']
endif
if plugin_label_enabled
plugin_pages += ['yambar-modules-label.5.scd']
endif
if plugin_network_enabled
plugin_pages += ['yambar-modules-network.5.scd']
endif
if plugin_niri_language_enabled
plugin_pages += ['yambar-modules-niri-language.5.scd']
endif
if plugin_niri_workspaces_enabled
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
endif
if plugin_pipewire_enabled
plugin_pages += ['yambar-modules-pipewire.5.scd']
endif
if plugin_pulse_enabled
plugin_pages += ['yambar-modules-pulse.5.scd']
endif
if plugin_removables_enabled
plugin_pages += ['yambar-modules-removables.5.scd']
endif
if plugin_river_enabled
plugin_pages += ['yambar-modules-river.5.scd']
endif
if plugin_script_enabled
plugin_pages += ['yambar-modules-script.5.scd']
endif
if plugin_sway_xkb_enabled
plugin_pages += ['yambar-modules-sway-xkb.5.scd']
endif
if plugin_xkb_enabled
plugin_pages += ['yambar-modules-xkb.5.scd']
endif
foreach man_src : ['yambar.1.scd',
'yambar.5.scd',
'yambar-decorations.5.scd',
'yambar-modules.5.scd',
'yambar-particles.5.scd',
'yambar-tags.5.scd'] + plugin_pages
foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
'yambar-modules-alsa.5.scd', 'yambar-modules-backlight.5.scd',
'yambar-modules-battery.5.scd', 'yambar-modules-clock.5.scd',
'yambar-modules-foreign-toplevel.5.scd',
'yambar-modules-i3.5.scd', 'yambar-modules-label.5.scd',
'yambar-modules-mpd.5.scd', 'yambar-modules-network.5.scd',
'yambar-modules-removables.5.scd', 'yambar-modules-river.5.scd',
'yambar-modules-script.5.scd', 'yambar-modules-sway-xkb.5.scd',
'yambar-modules-sway.5.scd', 'yambar-modules-xkb.5.scd',
'yambar-modules-xwindow.5.scd', 'yambar-modules.5.scd',
'yambar-particles.5.scd', 'yambar-tags.5.scd']
parts = man_src.split('.')
name = parts[-3]
section = parts[-2]
@ -87,7 +23,7 @@ foreach man_src : ['yambar.1.scd',
out,
output: out,
input: man_src,
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
capture: true,
install: true,
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))

View file

@ -23,7 +23,7 @@ This decoration sets the particles background color.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| color
: color
: yes
@ -49,7 +49,7 @@ bottom of the particle.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| size
: int
: yes
@ -70,74 +70,9 @@ content:
color: ff0000ff
```
# OVERLINE
Similar to _underline_, this decoration renders a line of configurable
size and color at the top of the particle.
## CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| size
: int
: yes
: The size (height/thickness) of the line, in pixels
| color
: color
: yes
: The color of the line. See *yambar*(5) for format.
## EXAMPLES
```
content:
string:
deco:
overline:
size: 2
color: ff0000ff
```
# BORDER
This decoration renders a border of configurable size (i.e border
width) around the particle.
## CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| color
: color
: yes
: The color of the line. See *yambar*(5) for format.
| size
: int
: no
: Border width, in pixels. Defaults to 1px.
## EXAMPLES
```
content:
string:
deco:
border:
size: 2
color: ff0000ff
```
# STACK
This particle combines multiple decorations.
This particles combines multiple decorations.
## CONFIGURATION

View file

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

View file

@ -11,7 +11,7 @@ _/sys/class/backlight_, and uses *udev* to monitor for changes.
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| brightness
: range
: The current brightness level, in absolute value

View file

@ -8,22 +8,11 @@ battery - This module reads battery status
This module reads battery status from _/sys/class/power_supply_ and
uses *udev* to monitor for changes.
Note that it is common (and "normal") for batteries to be in the state
*unknown* under certain conditions.
For example, some have been seen to enter the *unknown* state when
charging and the capacity reaches ~90%. The battery then stays in
*unknown*, rather than *charging*, until it has been fully charged and
enters the state *full*.
This does not happen with all batteries, and other batteries may enter
the state *unknown* under other conditions.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| name
: string
: Battery device name
@ -49,7 +38,7 @@ the state *unknown* under other conditions.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| name
: string
: yes
@ -57,20 +46,7 @@ the state *unknown* under other conditions.
| poll-interval
: int
: no
: How often, in milliseconds, to poll for capacity changes
(default=*60000*). Set to `0` to disable polling (*warning*: many
batteries do not support asynchronous reporting). Cannot be less
than 250ms.
| battery-scale
: int
: no
: How much to scale down the battery charge amount. Some batteries
report too high resulting in bad discharge estimates. Default=1.
| smoothing-secs
: int
: no
: How many seconds to perform smoothing over for battery discharge
estimates. Default=100s.
: How often, in seconds, to poll for capacity changes (default=*60*). Set to `0` to disable polling (*warning*: many batteries do not support asynchronous reporting).
# EXAMPLES
@ -79,7 +55,7 @@ bar:
left:
- battery:
name: BAT0
poll-interval: 30000
poll-interval: 30
content:
string: {text: "BAT: {capacity}% {estimate}"}
```

View file

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

View file

@ -1,79 +0,0 @@
yambar-modules-cpu(5)
# NAME
cpu - This module provides the CPU usage
# DESCRIPTION
This module reports CPU usage, in percent. The _content_ particle is a
template that is instantiated once for each core, and once for the
total CPU usage.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| id
: int
: Core ID. 0..n represents individual cores, and -1 represents the
total usage
| cpu
: range
: Current usage of CPU core {id}, in percent
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| poll-interval
: int
: no
: Refresh interval of the CPU usage stats in milliseconds
(default=500). Cannot be less then 250ms.
# EXAMPLES
## Display total CPU usage as a number
```
bar:
left:
- cpu:
poll-interval: 2500
content:
map:
conditions:
id < 0:
- string: {text: , font: Font Awesome 6 Free:style=solid}
- string: {text: "{cpu}%"}
```
## Display a vertical bar for each core
```
bar:
left:
- cpu:
poll-interval: 2500
content:
map:
conditions:
id >= 0:
- ramp:
tag: cpu
items:
- string: {text: ▁}
- string: {text: ▂}
- string: {text: ▃}
- string: {text: ▄}
- string: {text: ▅}
- string: {text: ▆}
- string: {text: ▇}
- string: {text: █}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -1,85 +0,0 @@
yambar-modules-disk-io(5)
# NAME
disk-io - This module keeps track of the amount of bytes being
read/written from/to disk. It can distinguish between all partitions
currently present in the machine.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| device
: string
: Name of the device being tracked (use the command *lsblk* to see these).
There is a special device, "Total", that reports the total stats
for the machine
| is_disk
: boolean
: whether or not the device is a disk (e.g., sda, sdb) or a partition
(e.g., sda1, sda2, ...). "Total" is advertised as a disk.
| read_speed
: int
: bytes read, in bytes/s
| write_speed
: int
: bytes written, in bytes/s
| ios_in_progress
: int
: number of ios that are happening at the time of polling
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| poll-interval
: int
: no
: Refresh interval of disk's stats in milliseconds (default=500).
Cannot be less then 250ms.
# EXAMPLES
This reports the total amount of bytes being read and written every second,
formatting in b/s, kb/s, mb/s, or gb/s, as appropriate.
```
bar:
left:
- disk-io:
poll-interval: 1000
content:
map:
conditions:
device == Total:
list:
items:
- string: {text: "Total read: "}
- map:
default: {string: {text: "{read_speed} B/s"}}
conditions:
read_speed > 1073741824:
string: {text: "{read_speed:gib} GB/s"}
read_speed > 1048576:
string: {text: "{read_speed:mib} MB/s"}
read_speed > 1024:
string: {text: "{read_speed:kib} KB/s"}
- string: {text: " | "}
- string: {text: "Total written: "}
- map:
default: {string: {text: "{write_speed} B/s"}}
conditions:
write_speed > 1073741824:
string: {text: "{write_speed:gib} GB/s"}
write_speed > 1048576:
string: {text: "{write_speed:mib} MB/s"}
write_speed > 1024:
string: {text: "{write_speed:kib} KB/s"}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -1,107 +0,0 @@
yambar-modules-dwl(5)
# NAME
dwl - This module provides information about dwl tags, and information.
# DESCRIPTION
This module provides a map of each tags present in dwl.
Each tags has its _id_, its _name_, its status (_selected_, _empty_, _urgent_)
and the global data like _title_, _appid_, _fullscreen_, _floating_,
_selmon_, and _layout_). The tags start a 1. For needs where
you only want information about the global data and not the _tags_,
there is a tag with the id _0_ that contains only the global data.
This module will track *only* the monitor where yambar was launched on.
If you have a multi monitor setup, please launch yambar on each of your
monitors.
Please, be aware that only *one instance* of this module is supported.
Running multiple instances at the same time may result in
*undefined behavior*.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| id
: int
: dwl tag id.
| name
: string
: The name of the tag (defaults to _id_ if not set).
| selected
: bool
: True if the tag is currently selected.
| empty
: bool
: True if there are no windows in the tag.
| urgent
: bool
: True if the tag has the urgent flag set.
| title
: string
: The currently focused window's title.
| appid
: string
: The currently focused window's application id.
| fullscreen
: bool
: True if there is a fullscreen window in the current tag.
| floating
: bool
: True if there is a floating window in the current tag.
| selmon
: bool
: True if the monitor is actually focused.
| layout
: string
: The actual layout name of the tag.
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| number-of-tags
: int
: yes
: The number of defined tags in the dwl `config.def.h`.
| name-of-tags
: list
: false
: The name of the tags (must have the same length that _number-of-tags_).
| dwl-info-filename
: string
: yes
: The filepath to the log emitted by dwl when running.
# EXAMPLES
```
bar:
left:
- dwl:
number-of-tags: 9
dwl-info-filename: "/home/ogromny/dwl_info"
name-of-tags: [ , , , , , , , ,  ]
content:
list:
items:
- map:
conditions:
# default tag
id == 0: {string: {text: "{layout} {title}"}}
selected: {string: {text: "-> {name}"}}
~empty: {string: {text: "{name}"}}
urgent: {string: {text: "=> {name} <="}}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

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

View file

@ -22,13 +22,10 @@ with the _application_ and _title_ tags to replace the X11-only
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| name
: string
: The workspace name
| output
: string
: The output (monitor) the workspace is on
| visible
: bool
: True if the workspace is currently visible (on any output)
@ -38,9 +35,6 @@ with the _application_ and _title_ tags to replace the X11-only
| urgent
: bool
: True if the workspace has the urgent flag set
| empty
: bool
: True if the workspace is empty (Sway only)
| state
: string
: One of *urgent*, *focused*, *unfocused* or *invisible* (note:
@ -60,7 +54,7 @@ with the _application_ and _title_ tags to replace the X11-only
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| content
: associative array
: yes
@ -70,11 +64,7 @@ with the _application_ and _title_ tags to replace the X11-only
| sort
: enum
: no
: How to sort the list of workspaces; one of _none_, _native_, _ascending_ or _descending_, defaults to _none_. Use _native_ to sort numbered workspaces only.
| strip-workspace-numbers
: bool
: no
: If true, *N:* prefixes will be stripped from workspace names. Useful together with *sort*, to have the workspace order fixed.
: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
| persistent
: list of strings
: no
@ -105,9 +95,10 @@ bar:
content:
"":
map:
tag: state
default: {string: {text: "{name}"}}
conditions:
state == focused: {string: {text: "{name}*"}}
values:
focused: {string: {text: "{name}*"}}
current: { string: {text: "{application}: {title}"}}
```

View file

@ -1,52 +0,0 @@
yambar-modules-mem(5)
# NAME
mem - This module provides the memory usage
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| free
: int
: Free memory in bytes
| used
: int
: Used memory in bytes
| total
: int
: Total memory in bytes
| percent_free
: range
: Free memory in percent
| percent_used
: range
: Used memory in percent
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| poll-interval
: string
: no
: Refresh interval of the memory usage stats in milliseconds
(default=500). Cannot be less then 250ms.
# EXAMPLES
```
bar:
left:
- mem:
poll-interval: 2500
content:
string: {text: "{used:mb}MB"}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -7,7 +7,7 @@ mpd - This module provides MPD status such as currently playing artist/album/son
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| state
: string
: One of *offline*, *stopped*, *paused* or *playing*
@ -20,9 +20,6 @@ mpd - This module provides MPD status such as currently playing artist/album/son
| consume
: bool
: True if the *consume* flag is set
| single
: bool
: True if the *single* flag is set
| volume
: range
: Volume of MPD in percentage
@ -35,9 +32,6 @@ mpd - This module provides MPD status such as currently playing artist/album/son
| title
: string
: Title of currently playing song (also valid in *paused* state)
| file
: string
: Filename or URL of currently playing song (also valid in *paused* state)
| pos
: string
: *%M:%S*-formatted string describing the song's current position
@ -59,7 +53,7 @@ mpd - This module provides MPD status such as currently playing artist/album/son
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| host
: string
: yes

View file

@ -6,31 +6,20 @@ network - This module monitors network connection state
# DESCRIPTION
This module monitors network connection state; disconnected/connected
state and MAC/IP addresses. It instantiates the provided _content_
particle for each network interface.
state and MAC/IP addresses.
Note: while the module internally tracks all assigned IPv4/IPv6
addresses, it currently exposes only a single IPv4 and a single IPv6
address per network interface.
address.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| name
: string
: Network interface name
| type
: string
: Interface type (*ether*, *wlan*, *loopback*, or *ARPHRD_NNN*, where
*N* is a number).
| kind
: string
: Interface kind. Empty for non-virtual interfaces. For virtual
interfaces, this value is taken from the _IFLA\_INFO\_KIND_ netlink
attribute. Examples of valid values are *bond*, *bridge*, *gre*, *tun*
and *veth*.
| index
: int
: Network interface index
@ -56,21 +45,12 @@ address per network interface.
| signal
: int
: Signal strength, in dBm (Wi-Fi only)
| quality
: range
: Quality of the signal, in percent (Wi-Fi only)
| rx-bitrate
: int
: RX bitrate, in bits/s
: RX bitrate, in Mbit/s
| tx-bitrate
: int
: TX bitrate in bits/s
| dl-speed
: int
: Download speed in bits/s
| ul-speed
: int
: Upload speed in bits/s
: TX bitrate in Mbit/s
# CONFIGURATION
@ -78,46 +58,26 @@ address per network interface.
[[ *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_
:[ *Description*
| name
: string
: yes
: Name of network interface to monitor
| poll-interval
: int
: no
: Periodically (in milliseconds) update the signal, quality, rx+tx bitrate, and
ul+dl speed tags (default=0). Setting it to 0 disables updates. Cannot be less
than 250ms.
: Periodically (in seconds) update the signal and rx+tx bitrate tags.
# EXAMPLES
Display all Ethernet (including WLAN) devices. This excludes loopback,
bridges etc.
```
bar:
left:
- network:
name: wlp3s0
content:
map:
conditions:
type == ether || type == wlan:
map:
default:
string: {text: "{name}: {state} ({ipv4})"}
conditions:
ipv4 == "":
string: {text: "{name}: {state}"}
string: {text: "{name}: {state} ({ipv4})"}
```
# SEE ALSO

View file

@ -1,34 +0,0 @@
yambar-modules-niri-language(5)
# NAME
niri-language - This module provides information about niri's currently
selected language.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| language
: string
: The currently selected language.
# CONFIGURATION
No additional attributes supported, only the generic ones (see
*GENERIC CONFIGURATION* in *yambar-modules*(5))
# EXAMPLES
```
bar:
left:
- niri-language:
content:
string: {text: "{language}"}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -1,60 +0,0 @@
yambar-modules-niri-workspaces(5)
# NAME
niri-workspaces - This module provides information about niri workspaces.
# DESCRIPTION
This module provides a map of each workspace present in niri.
Each workspace has its _id_, _name_, and its status (_focused_,
_active_, _empty_). The workspaces are sorted by their ids.
This module will *only* track the monitor where yambar was launched.
If you have a multi monitor setup, please launch yambar on each
individual monitor to track its workspaces.
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| id
: int
: The workspace id.
| name
: string
: The name of the workspace.
| active
: bool
: True if the workspace is currently visible on the current output.
| focused
: bool
: True if the workspace is currently focused.
| empty
: bool
: True if the workspace contains no window.
# CONFIGURATION
No additional attributes supported, only the generic ones (see
*GENERIC CONFIGURATION* in *yambar-modules*(5))
# EXAMPLES
```
bar:
left:
- niri-workspaces:
content:
map:
default: {string: {text: "| {id}"}}
conditions:
active: {string: {text: "-> {id}"}}
~empty: {string: {text: "@ {id}"}}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -1,91 +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*
| content
: particle
: yes
: Unlike other modules, _content_ is a template particle that will be
expanded twice (i.e. into a list of two elements). The first
element is the 'sink', and the second element the 'source'.
# EXAMPLES
```
bar:
left:
- pipewire:
anchors:
volume: &volume
conditions:
muted: {string: {text: "{linear_volume}%", foreground: ff0000ff}}
~muted: {string: {text: "{linear_volume}%"}}
content:
list:
items:
- map:
conditions:
type == "sink":
map:
conditions:
icon == "audio-headset-bluetooth":
string: {text: "🎧 "}
default:
- ramp:
tag: linear_volume
items:
- string: {text: "🔈 "}
- string: {text: "🔉 "}
- string: {text: "🔊 "}
type == "source":
- string: {text: "🎙 "}
- map:
<<: *volume
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -1,67 +0,0 @@
yambar-modules-pulse(5)
# NAME
pulse - Monitors a PulseAudio source and/or sink
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
| online
: bool
: True when connected to the PulseAudio server
| sink_online
: bool
: True when the sink is present
| source_online
: bool
: True when the source is present
| sink_percent
: range
: Sink volume level, as a percentage
| source_percent
: range
: Source volume level, as a percentage
| sink_muted
: bool
: True if the sink is muted, otherwise false
| source_muted
: bool
: True if the source is muted, otherwise false
| sink_port
: string
: Description of the active sink port
| source_port
: string
: Description of the active source port
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| sink
: string
: no
: Name of sink to monitor (default: _@DEFAULT\_SINK@_).
| source
: string
: no
: Name of source to monitor (default: _@DEFAULT\_SOURCE@_).
# EXAMPLES
```
bar:
left:
- pulse:
content:
string: {text: "{sink_percent}% ({sink_port})"}
```
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -12,7 +12,7 @@ instantiates the provided _content_ particle for each detected drive.
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| vendor
: string
: Name of the drive vendor
@ -22,10 +22,6 @@ instantiates the provided _content_ particle for each detected drive.
| optical
: bool
: True if the drive is an optical drive (CD-ROM, DVD-ROM etc)
| audio
: bool
: True if an optical drive has an audio CD inserted (i.e. this
property is always false for non-optical drives).
| device
: string
: Volume device name (typically */dev/sd?*)
@ -48,7 +44,7 @@ instantiates the provided _content_ particle for each detected drive.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| left-spacing
: int
: no
@ -74,12 +70,13 @@ bar:
- removables:
content:
map:
conditions:
~mounted:
tag: mounted
values:
false:
string:
on-click: udisksctl mount -b {device}
text: "{label}"
mounted:
true:
string:
on-click: udisksctl unmount -b {device}
text: "{label}"

View file

@ -13,18 +13,17 @@ It has an interface similar to the i3/sway module.
The configuration for the river module specifies one _title_ particle,
which will be instantiated once for each seat, with tags representing
the seats' name, the title of the seats' currently focused view, and
its current river "mode".
the seats' name and the title of the seats' currently focused view.
It also specifies a _content_ template particle, which is instantiated
once for all 32 river tags. This means you probably want to use a
*map* particle to hide unused river tags.
# TAGS (for the "content" particle)
# TAGS
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| id
: int
: River tag number
@ -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).
| state
: string
: Set to *urgent* if _urgent_ is true, *focused* if _focused_ is true,
*unfocused* if _visible_ is true, but _focused_ is false, or
*invisible* if the river tag is not visible on any monitors.
# TAGS (for the "title" particle)
[[ *Name*
:[ *Type*
:< *Description*
: Set to *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.
| seat
: string
: The name of the seat.
: The name of the seat (*title* particle only, see CONFIGURATION)
| title
: string
: The seat's focused view's title.
| mode
: string
: The seat's current mode (entered with e.g. *riverctl enter-mode foobar*).
| layout
: string
: Current layout of the output currently focused by the seat.
: The seat's focused view's title (*title* particle only, see CONFIGURATION)
# CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| title
: particle
: no
@ -92,12 +76,13 @@ once for all 32 river tags. This means you probably want to use a
bar:
left:
- river:
title: {string: { text: "{seat} - {title} ({layout}/{mode})" }}
title: {string: { text: "{seat} - {title}" }}
content:
map:
conditions:
~occupied: {empty: {}}
occupied:
tag: occupied
values:
false: {empty: {}}
true:
string:
margin: 5
text: "{id}: {state}"

View file

@ -16,7 +16,7 @@ configurable amount of time.
In continuous mode, the script is executed once. It will typically run
in a loop, sending an updated tag set whenever it needs, or wants
to. The last tag set is used (displayed) by yambar until a new tag set
is received. This mode is intended to be used by scripts that depend
is received. This mode is intended to be used by scripts that depends
on non-polling methods to update their state.
Tag sets, or _transactions_, are separated by an empty line
@ -66,21 +66,19 @@ User defined.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| path
: string
: yes
: Path to script/binary to execute. Must either be an absolute path,
or start with *~/*.
: Path to script/binary to execute. Must be an absolute path.
| args
: list of strings
: no
: Arguments to pass to the script/binary.
| poll-interval
: integer
: no
: Number of milliseconds between each script run. If unset, or set to
0, continuous mode is used.
: Number of seconds between each script run. If unset, continuous mode
is used.
# EXAMPLES
@ -115,36 +113,6 @@ bar:
content: {string: {text: "{test}"}}
```
Another example use case of this module could be to display currently playing
song or other media from players that support MPRIS (Media Player Remote
Interfacing Specification):
```
bar:
center:
- script:
path: /usr/bin/playerctl
args:
- "--follow"
- "metadata"
- "-f"
- |
status|string|{{status}}
artist|string|{{artist}}
title|string|{{title}}
content:
map:
conditions:
status == Paused: {empty: {}}
status == Playing:
content: {string: {text: "{artist} - {title}"}}
```
The above snippet runs a _playerctl_ utility in _--follow_ mode, reacting to
media updates on DBUS and outputting status, artist and title of media being
played in a format that is recognized by yambar. See _playerctl_ documentation
for more available metadata fields and control over which players get used.
# SEE ALSO
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)

View file

@ -16,7 +16,7 @@ instantiated from this template, and represents an input device.
[[ *Name*
:[ *Type*
:< *Description*
:[ *Description*
| id
: string
: Input device identifier
@ -29,7 +29,7 @@ instantiated from this template, and represents an input device.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| identifiers
: list of strings
: yes

View file

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

View file

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

View file

@ -38,7 +38,7 @@ For example, to render _backlight_ as " 20%", you could use:
```
content:
- string:
font: Font Awesome 6 Free:style=solid:pixelsize=14
font: Font Awesome 5 Free:style=solid:pixelsize=14
text: 
- string:
font: Adobe Helvetica:pixelsize=12
@ -68,17 +68,20 @@ in red.
```
content:
map:
conditions:
~carrier: {empty: {}}
carrier:
tag: carrier
values:
false: {empty: {}}
true:
map:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}}
conditions:
state == up:
values:
up:
map:
tag: ipv4
default: {string: {text: , font: *awesome}}
conditions:
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
values:
"": {string: {text: , font: *awesome, foreground: ffffff66}}
```
## Use yaml anchors
@ -91,7 +94,7 @@ In these cases, you can define an anchor point, either at top-level,
or in a module's _anchors_ attribute:
```
awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14
awesome: &awesome Font Awesome 5 Free:style=solid:pixelsize=14
```
@ -110,7 +113,7 @@ following attributes are supported by all modules:
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| content
: particle
: yes
@ -142,28 +145,14 @@ Available modules have their own pages:
*yambar-modules-clock*(5)
*yambar-modules-cpu*(5)
*yambar-modules-disk-io*(5)
*yambar-modules-dwl*(5)
*yambar-modules-foreign-toplevel*(5)
*yambar-modules-i3*(5)
*yambar-modules-label*(5)
*yambar-modules-mem*(5)
*yambar-modules-mpd*(5)
*yambar-modules-network*(5)
*yambar-modules-pipewire*(5)
*yambar-modules-pulse*(5)
*yambar-modules-removables*(5)
*yambar-modules-river*(5)
@ -174,10 +163,6 @@ Available modules have their own pages:
*yambar-modules-sway*(5)
*yambar-modules-niri-language*(5)
*yambar-modules-niri-workspaces*(5)
*yambar-modules-xkb*(5)
*yambar-modules-xwindow*(5)

View file

@ -12,7 +12,7 @@ following attributes are supported by all particles:
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| left-margin
: int
: no
@ -31,76 +31,20 @@ following attributes are supported by all particles:
: Font to use. Note that this is an inherited attribute; i.e. you can
set it on e.g. a _list_ particle, and it will apply to all
particles in the list.
| font-shaping
: enum
: no
: font-shaping; one of _full_ or _none_. When set to _full_ (the
default), strings will be "shaped" using HarfBuzz. Requires support
in fcft.
| foreground
: color
: no
: Foreground (text) color. Just like _font_, this is an inherited attribute.
| on-click
: associative array/string
: no
: When set to a string, executes the string as a command when the
particle is left-clicked. Tags can be used. Note that the string is
*not* executed in a shell. Environment variables are not expanded.
*~/* is expanded, but only in the first argument. The same applies
to all attributes associated with it, below.
| on-click.left
: string
: no
: Command to execute when the particle is left-clicked.
| on-click.right
: string
: no
: Command to execute when the particle is right-clicked.
| on-click.middle
: string
: no
: Command to execute when the particle is middle-clicked.
| on-click.wheel-up
: string
: no
: Command to execute every time a 'wheel-up' event is triggered.
| on-click.wheel-down
: string
: no
: Command to execute every time a 'wheel-down' event is triggered.
| on-click.previous
: string
: no
: Command to execute when the particle is clicked with the 'previous' button.
| on-click.next
: string
: no
: Command to execute when the particle is clicked with the 'next' button.
: Command to execute when the particle is clicked. Tags can be
used. Note that the string is *not* executed in a shell.
| deco
: decoration
: no
: Decoration to apply to the particle. See *yambar-decorations*(5)
## EXAMPLES:
*on-click* as a string (handles left click):
```
content:
<particle>:
on-click: command args
```
*on-click* as an associative array (handles other buttons):
```
content:
<particle>:
on-click:
left: command-1
wheel-up: command-3
wheel-down: command-4
```
# STRING
This is the most basic particle. It takes a format string, consisting
@ -115,7 +59,7 @@ of free text mixed with tag specifiers.
| text
: string
: yes
: Format string. Tags are specified with _{tag_name}_. Some tag types
: Format string. Tags are spcified with _{tag_name}_. Some tag types
have suffixes that can be appended (e.g. _{tag_name:suffix}_). See
*yambar-modules*(5)).
| max
@ -123,9 +67,9 @@ of free text mixed with tag specifiers.
: no
: Sets the rendered string's maximum length. If the final string's
length exceeds this, the rendered string will be truncated, and
"…" will be appended. Note that the trailing "…" is
"..." will be appended. Note that the trailing "..." are
*included* in the maximum length. I.e. if you set _max_ to '5', you
will only get *4* characters from the string.
will only get *2* characters from the string.
## EXAMPLES
@ -155,7 +99,7 @@ content:
This particle is a list (or sequence, if you like) of other
particles. It can be used to render e.g. _string_ particles with
different font and/or color formatting. Or any other particle
different font and/or color formatting. Or ay other particle
combinations.
But note that this means you *cannot* set any attributes on the _list_
@ -166,7 +110,7 @@ particle itself.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| items
: list
: yes
@ -214,165 +158,51 @@ content:
- string: ...
```
Note that the short form has a hard-coded *right-spacing* of 2. This
cannot be changed. If you want a different spacing, you must use an
explicit list particle (i.e. the long form).
# MAP
This particle maps the values of a specific tag to different
particles based on conditions. A condition takes either the form of:
```
<tag> <operation> <value>
```
Or, for boolean tags:
```
<tag>
```
Where <tag> is the tag you would like to map, <operation> is one of:
[- ==
:- !=
:- >=
:- >
:- <=
:- <
and <value> is the value you would like to compare it to. *If the
value contains any non-alphanumerical characters, you must
surround it with ' \" ' *:
```
"hello world"
"@#$%"
```
Negation is done with a preceding '~':
```
~<tag>
~<condition>
```
To match for empty strings, use ' "" ':
```
<tag> == ""
```
String glob matching
To perform string matching using globbing with "\*" & "?" characters:
\* Match any zero or more characters. ? Match exactly any one
character.
```
<tag> ~~ "hello*"
```
Will match any string starting with "hello", including "hello",
"hello1", "hello123", etc.
```
<tag> ~~ "hello?"
```
Will match any string starting with "hello" followed by any single
character, including "hello1", "hello-", but not "hello".
Furthermore, you may use the boolean operators:
[- &&
:- ||
in order to create more complex conditions:
```
<condition1> && <condition2>
```
You may surround <condition> with parenthesis for clarity or
specifying precedence:
```
(<condition>)
<condition1> && (<condition2> || <condition3>)
```
In addition to explicit tag values, you can also specify a
particles. In addition to explicit tag values, you can also specify a
default/fallback particle.
Note that conditions are evaluated in the order they appear. *If
multiple conditions are true, the first one will be used*. This means
that in a configuration such as:
```
tx-bitrate > 1000:
tx-bitrate > 1000000:
```
the second condition would never run, since whenever the second
condition is true, the first is also true. The correct way of doing
this would be to invert the order of the conditions:
```
tx-bitrate > 1000000:
tx-bitrate > 1000:
```
## CONFIGURATION
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
| conditions
:[ *Description*
| tag
: string
: yes
: The tag (name of) which values should be mapped
| values
: associative array
: yes
: An associative array of conditions (see above) mapped to particles
: An associative array of tag values mapped to particles
| default
: particle
: no
: Default particle to use, none of the conditions are true
: Default particle to use, when tag's value does not match any of the
mapped values.
## EXAMPLES
```
content:
map:
tag: tag_name
default:
string:
text: this is the default particle; the tag's value is now {tag_name}
conditions:
tag == one_value:
values:
one_value:
string:
text: tag's value is now one_value
tag == another_value:
another_value:
string:
text: tag's value is now another_value
```
For a boolean tag:
```
content:
map:
conditions:
tag:
string:
text: tag is true
~tag:
string:
text: tag is false
```
# RAMP
This particle uses a range tag to index into an array of
@ -385,7 +215,7 @@ indicator.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| tag
: string
: yes
@ -396,18 +226,6 @@ indicator.
: List of particles. Note that the tag value is *not* used as-is; its
minimum and maximum values are used to map the tag's range to the
particle list's range.
| min
: int
: no
: If present this will be used as a lower bound instead of the tags
minimum value. Tag values falling outside the defined range will
get clamped to min/max.
| max
: int
: no
: If present this will be used as an upper bound instead of the tags
maximum value. Tag values falling outside the defined range will
get clamped to min/max.
## EXAMPLES
@ -440,7 +258,7 @@ itself when needed.
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| tag
: string
: yes
@ -476,7 +294,7 @@ itself when needed.
```
content:
progress-bar:
progres-bar:
tag: tag_name
length: 20
start: {string: {text: ├}}

View file

@ -11,7 +11,7 @@ their information. Each module defines its own set of tags.
The available tag *types* are:
[[ *Type*
:< *Description*
:[ *Description*
| string
: Value is a string. Rendered as-is by the _string_ particle.
| int
@ -58,77 +58,39 @@ be used.
[[ *Formatter*
:[ *Kind*
:[ *Applies to*
:< *Description*
| [0]<number>[.]
: format
: Numeric tags (integer and floats)
: The width reserved to the field. The leading '0' is optional and
indicates zero padding, as opposed to space padding. The trailing
'.' is also optional
| .<number>
: format
: Float tags
: How many decimals to print
| [0]<N>[.]<M>
: format
: N: numeric tags, M: float tags
: Combined version of the two previous formatters
:[ *Description*
:[ *Applies to*]
| hex
: format
: All tag types
: Renders a tag's value in hex
: All tag types
| oct
: format
: All tag types
: Renders a tag's value in octal
: All tag types
| %
: format
: Range tags
: Renders a range tag's value as a percentage value
| /N
: format
: All tag types
: Renders a tag's value (in decimal) divided by N
: Range tags
| kb, mb, gb
: 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
: Renders a tag's value (in decimal) divided by 1000, 1000^2 or
1000^3. Note: no unit suffix is appended
| kib, mib, gib
: format
: Same as *kb*, *mb* and *gb*, but divide by 1000^n instead of 1024^n.
: All tag types
: Same as *kb*, *mb* and *gb*, but divide by 1024^n instead of 1000^n.
| min
: selector
: Range tags
: Renders a range tag's minimum value
: Range tags
| max
: selector
: Range tags
: Renders a range tag's maximum value
: Range tags
| unit
: selector
: Realtime tags
: 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_
Print PID to this file, or FD, when successfully started. The file
(or FD) is closed immediately after writing the PID. When a _FILE_
as been specified, the file is unlinked upon exiting.
as been specified, the file is unlinked exit.
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
Log level, used both for log output on stderr as well as
syslog. Default: _warning_.
syslog. Default: _info_.
*-l*,*--log-colorize*=[{*never*,*always*,*auto*}]
Enables or disables colorization of log output on stderr.

View file

@ -12,10 +12,9 @@ and reference them using anchors.
Besides the normal yaml types, there are a couple of yambar specific
types that are frequently used:
- *font*: this is a comma separated list of fonts in _fontconfig_
format. Example of valid values:
- Font Awesome 6 Brands
- Font Awesome 6 Free:style=solid
- *font*: this is a string in _fontconfig_ format. Example of valid values:
- Font Awesome 5 Brands
- Font Awesome 5 Free:style=solid
- Dina:pixelsize=10:slant=italic
- Dina:pixelsize=10:weight=bold
- *color*: an rgba hexstring; _RRGGBBAA_. Examples:
@ -23,17 +22,12 @@ types that are frequently used:
- 000000ff: black, no transparency
- 00ff00ff: green, no transparency
- ff000099: red, semi-transparent
- *environment reference*: a string that contains format ${VAR}. This will be
replaced by the value of the environment variable VAR. Example:
- ${HOME}
- ${HOME}/.config/yambar
- ENV is ${ENV}, ENV2 is ${ENV2}
# FORMAT
[[ *Name*
:[ *Type*
:[ *Req*
:< *Description*
:[ *Description*
| height
: int
: yes
@ -54,8 +48,7 @@ types that are frequently used:
| layer
: string
: no
: Layer to put bar on. One of _overlay_, _top_, _bottom_ or
_background_. Wayland only. Default: _bottom_.
: Layer to put bar on. One of _top_ or _bottom_. Wayland only
| left-spacing
: int
: no
@ -131,17 +124,7 @@ types that are frequently used:
| font
: font
: no
: Default font to use in modules and particles. May also be a comma
separated list of several fonts, in which case the first font is
the primary font, and the rest fallback fonts. These are yambar
custom fallback fonts that will be searched before the fontconfig
provided fallback list.
| font-shaping
: enum
: no
: Default setting for font-shaping, for use in particles. One of
_full_ or _none_. When set to _full_ (the default), strings will be
"shaped" using HarfBuzz. Requires support in fcft.
: Default font to use in modules and particles
| foreground
: color
: no
@ -178,8 +161,8 @@ bar:
right:
- clock:
content:
- string: {text: "{time}"}
content:
- string: {text: "{time}"}
```
# FILES

View file

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

View file

@ -1,58 +0,0 @@
hack: &hack Hack Nerd Font:pixelsize=13
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
bar:
height: 40
location: top
font: JuliaMono:pixelsize=10
spacing: 2
margin: 0
layer: bottom
foreground: eeeeeeff
background: 2E3440dd
left:
- river:
anchors:
- base: &river_base
left-margin: 10
right-margin: 13
default: {string: {text: , font: *hack}}
conditions:
id == 1: {string: {text: ﳐ, font: *hack}}
id == 2: {string: {text: , font: *hack}}
id == 3: {string: {text: , font: *hack}}
id == 4: {string: {text: , font: *hack}}
id == 5: {string: {text: , font: *hack}}
id == 10: {string: {text: "scratchpad", font: *hack}}
id == 11: {string: {text: "work", font: *hack}}
content:
map:
on-click:
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
conditions:
state == urgent:
map:
<<: *river_base
deco: {background: {color: D08770ff}}
state == focused:
map:
<<: *river_base
deco: *bg_default
state == visible && ~occupied:
map:
<<: *river_base
state == visible && occupied:
map:
<<: *river_base
deco: *bg_default
state == unfocused:
map:
<<: *river_base
state == invisible && ~occupied: {empty: {}}
state == invisible && occupied:
map:
<<: *river_base
deco: {underline: {size: 3, color: ea6962ff}}

View file

@ -1,69 +0,0 @@
bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]}
bar:
height: 32
location: top
background: 000000ff
font: NotoSans:pixelsize=16
right:
- clock:
content:
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: "{date}", right-margin: 5}
- string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"}
- string: {text: "{time} "}
left:
- river:
anchors:
- base: &river_base
left-margin: 10
right-margin: 13
default: {string: {text: }}
conditions:
id == 1: {string: {text: 1}}
id == 2: {string: {text: 2}}
id == 3: {string: {text: 3}}
id == 4: {string: {text: 4}}
id == 5: {string: {text: 5}}
content:
map:
on-click:
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
conditions:
state == urgent:
map:
<<: *river_base
deco: {background: {color: D08770ff}}
state == focused:
map:
<<: *river_base
deco: *bg_default
state == visible && ~occupied:
map:
<<: *river_base
state == visible && occupied:
map:
<<: *river_base
deco: *bg_default
state == unfocused:
map:
<<: *river_base
state == invisible && ~occupied: {empty: {}}
state == invisible && occupied:
map:
<<: *river_base
deco: {underline: {size: 3, color: ea6962ff}}
center:
- foreign-toplevel:
content:
map:
conditions:
~activated: {empty: {}}
activated:
- string: {text: " {app-id}", foreground: ffa0a0ff}
- string: {text: ": {title}"}

View file

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

View file

@ -12,21 +12,22 @@
# {aur} int number of aur packages
# {pkg} int sum of both
#
# Examples configuration:
# Exemples configuration:
# - script:
# path: /absolute/path/to/pacman.sh
# args: []
# args: []
# content: { string: { text: "{pacman} + {aur} = {pkg}" } }
#
# To display a message when there is no update:
# - script:
# path: /absolute/path/to/pacman.sh
# args: []
# args: []
# content:
# map:
# tag: pkg
# default: { string: { text: "{pacman} + {aur} = {pkg}" } }
# conditions:
# pkg == 0: {string: {text: no updates}}
# values:
# 0: {string: {text: no updates}}
declare interval aur_helper pacman_num aur_num pkg_num
@ -47,9 +48,9 @@ while true; do
# Change interval
# NUMBER[SUFFIXE]
# Possible suffix:
# "s" seconds / "m" minutes / "h" hours / "d" days
# "s" seconds / "m" minutes / "h" hours / "d" days
interval="1h"
# Change your aur manager
aur_helper="paru"
@ -62,7 +63,7 @@ while true; do
else
aur_num=$("${aur_helper}" -Qmu | wc -l)
fi
pkg_num=$(( pacman_num + aur_num ))
printf -- '%s\n' "pacman|int|${pacman_num}"
@ -76,3 +77,4 @@ done
unset -v interval aur_helper pacman_num aur_num pkg_num
unset -f _err

View file

@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright>
<interface name="zriver_status_manager_v1" version="4">
<interface name="zriver_status_manager_v1" version="2">
<description summary="manage river status objects">
A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar.
@ -47,7 +47,7 @@
</request>
</interface>
<interface name="zriver_output_status_v1" version="4">
<interface name="zriver_output_status_v1" version="2">
<description summary="track output tags and focus">
This interface allows clients to receive information about the current
windowing state of an output.
@ -83,24 +83,9 @@
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
<event name="layout_name" since="4">
<description summary="name of the layout">
Sent once on binding the interface should a layout name exist and again
whenever the name changes.
</description>
<arg name="name" type="string" summary="layout name"/>
</event>
<event name="layout_name_clear" since="4">
<description summary="name of the layout">
Sent when the current layout name has been removed without a new one
being set, for example when the active layout generator disconnects.
</description>
</event>
</interface>
<interface name="zriver_seat_status_v1" version="3">
<interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent
@ -136,13 +121,5 @@
</description>
<arg name="title" type="string" summary="title of the focused view"/>
</event>
<event name="mode" since="3">
<description summary="the active mode changed">
Sent once on binding the interface and again whenever a new mode
is entered (e.g. with riverctl enter-mode foobar).
</description>
<arg name="name" type="string" summary="name of the mode"/>
</event>
</interface>
</protocol>

View file

@ -1,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
workdir=$(pwd)
cd "${src_dir}"
if git describe --tags > /dev/null 2>&1; then
git_version=$(git describe --always --tags)
else
# No tags available, happens in e.g. CI builds
git_version="${default_version}"
fi
git_version=$(git describe --always --tags)
git_branch=$(git rev-parse --abbrev-ref HEAD)
cd "${workdir}"

51
log.c
View file

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

71
log.h
View file

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

64
main.c
View file

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

View file

@ -1,7 +1,7 @@
project('yambar', 'c',
version: '1.11.0',
version: '1.7.0',
license: 'MIT',
meson_version: '>=0.59.0',
meson_version: '>=0.53.0',
default_options: ['c_std=c18',
'warning_level=1',
'werror=true',
@ -12,15 +12,9 @@ plugs_as_libs = get_option('core-plugins-as-shared-libraries')
cc = meson.get_compiler('c')
if cc.has_function('memfd_create',
args: ['-D_GNU_SOURCE=200809L'],
prefix: '#include <sys/mman.h>')
add_project_arguments('-DMEMFD_CREATE', language: 'c')
endif
# Compute the relative path used by compiler invocations.
source_root = meson.current_source_dir().split('/')
build_root = meson.global_build_root().split('/')
build_root = meson.build_root().split('/')
relative_dir_parts = []
i = 0
in_prefix = true
@ -49,9 +43,7 @@ endif
# Common dependencies
dl = cc.find_library('dl')
m = cc.find_library('m')
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
libepoll = dependency('epoll-shim', required: false)
libinotify = dependency('libinotify', required: false)
threads = dependency('threads')
pixman = dependency('pixman-1')
yaml = dependency('yaml-0.1')
@ -73,7 +65,7 @@ backend_wayland = wayland_client.found() and wayland_cursor.found()
# "My" dependencies, fallback to subproject
tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist')
fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft')
fcft = dependency('fcft', version: ['>=2.4.0', '<3.0.0'], fallback: 'fcft')
add_project_arguments(
['-D_GNU_SOURCE'] +
@ -92,20 +84,16 @@ if backend_x11
c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [],
pic: plugs_as_libs)
xcb_stuff = declare_dependency(
link_with: xcb_stuff_lib,
dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr,
xcb_render, xcb_errors],
)
xcb_stuff = declare_dependency(link_with: xcb_stuff_lib)
install_headers('xcb.h', subdir: 'yambar')
endif
subdir('completions')
subdir('doc')
subdir('bar')
subdir('decorations')
subdir('particles')
subdir('modules')
subdir('doc')
env = find_program('env', native: true)
generate_version_sh = files('generate-version.sh')
@ -113,16 +101,14 @@ version = custom_target(
'generate_version',
build_always_stale: true,
output: 'version.h',
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@SOURCE_ROOT@', '@OUTPUT@'])
yambar = executable(
'yambar',
'char32.c', 'char32.h',
'color.h',
'config-verify.c', 'config-verify.h',
'config.c', 'config.h',
'decoration.h',
'font-shaping.h',
'log.c', 'log.h',
'main.c',
'module.c', 'module.h',
@ -131,7 +117,7 @@ yambar = executable(
'tag.c', 'tag.h',
'yml.c', 'yml.h',
version,
dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] +
dependencies: [bar, pixman, yaml, threads, dl, tllist, fcft] +
decorations + particles + modules,
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
export_dynamic: true,
@ -167,33 +153,3 @@ summary(
},
bool_yn: true
)
summary(
{
'ALSA': plugin_alsa_enabled,
'Backlight': plugin_backlight_enabled,
'Battery': plugin_battery_enabled,
'Clock': plugin_clock_enabled,
'CPU monitoring': plugin_cpu_enabled,
'Disk I/O monitoring': plugin_disk_io_enabled,
'dwl (dwm for Wayland)': plugin_dwl_enabled,
'Foreign toplevel (window tracking for Wayland)': plugin_foreign_toplevel_enabled,
'Memory monitoring': plugin_mem_enabled,
'Music Player Daemon (MPD)': plugin_mpd_enabled,
'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,50 +5,3 @@ option(
option(
'core-plugins-as-shared-libraries', type: 'boolean', value: false,
description: 'Compiles modules, particles and decorations as shared libraries, which are loaded on-demand')
option('plugin-alsa', type: 'feature', value: 'auto',
description: 'ALSA support')
option('plugin-backlight', type: 'feature', value: 'auto',
description: 'Backlight support')
option('plugin-battery', type: 'feature', value: 'auto',
description: 'Battery support')
option('plugin-clock', type: 'feature', value: 'auto',
description: 'Clock support')
option('plugin-cpu', type: 'feature', value: 'auto',
description: 'CPU monitoring support')
option('plugin-disk-io', type: 'feature', value: 'auto',
description: 'Disk I/O support')
option('plugin-dwl', type: 'feature', value: 'auto',
description: 'dwl (dwm for wayland) support')
option('plugin-foreign-toplevel', type: 'feature', value: 'auto',
description: 'Foreign toplevel (window tracking for Wayland) support')
option('plugin-mem', type: 'feature', value: 'auto',
description: 'Memory monitoring support')
option('plugin-mpd', type: 'feature', value: 'auto',
description: 'Music Player Daemon (MPD) support')
option('plugin-i3', type: 'feature', value: 'auto',
description: 'i3+Sway support')
option('plugin-label', type: 'feature', value: 'auto',
description: 'Label support')
option('plugin-network', type: 'feature', value: 'auto',
description: 'Network monitoring support')
option('plugin-pipewire', type: 'feature', value: 'auto',
description: 'Pipewire support')
option('plugin-pulse', type: 'feature', value: 'auto',
description: 'PulseAudio support')
option('plugin-removables', type: 'feature', value: 'auto',
description: 'Removables (USB sticks, CD-ROM etc) monitoring support')
option('plugin-river', type: 'feature', value: 'auto',
description: 'River support')
option('plugin-script', type: 'feature', value: 'auto',
description: 'Script support')
option('plugin-sway-xkb', type: 'feature', value: 'auto',
description: 'keyboard support for Sway')
option('plugin-niri-language', type: 'feature', value: 'auto',
description: 'language support for Niri')
option('plugin-niri-workspaces', type: 'feature', value: 'auto',
description: 'workspaces support for Niri')
option('plugin-xkb', type: 'feature', value: 'auto',
description: 'keyboard support for X11')
option('plugin-xwindow', type: 'feature', value: 'auto',
description: 'XWindow (window tracking for X11) support')

View file

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

View file

@ -27,7 +27,7 @@ struct module {
* specified number of milliseconds */
bool (*refresh_in)(struct module *mod, long milli_seconds);
const char *(*description)(const struct module *mod);
const char *(*description)(struct module *mod);
};
struct module *module_common_new(void);
@ -35,9 +35,9 @@ void module_default_destroy(struct module *mod);
struct exposable *module_begin_expose(struct module *mod);
/* List of attributes *all* modules implement */
#define MODULE_COMMON_ATTRS \
{"content", true, &conf_verify_particle}, {"anchors", false, NULL}, {"font", false, &conf_verify_font}, \
{"foreground", false, &conf_verify_color}, \
{ \
NULL, false, NULL \
}
#define MODULE_COMMON_ATTRS \
{"content", true, &conf_verify_particle}, \
{"anchors", false, NULL}, \
{"font", false, &conf_verify_font}, \
{"foreground", false, &conf_verify_color}, \
{NULL, false, NULL}

View file

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

View file

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

View file

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

View file

@ -1,22 +1,20 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <poll.h>
#include <sys/time.h>
#define LOG_MODULE "clock"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h"
struct private
{
struct private {
struct particle *label;
enum {
UPDATE_GRANULARITY_SECONDS,
@ -24,7 +22,6 @@ struct private
} update_granularity;
char *date_format;
char *time_format;
bool utc;
};
static void
@ -39,7 +36,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "clock";
}
@ -49,7 +46,7 @@ content(struct module *mod)
{
const struct private *m = mod->private;
time_t t = time(NULL);
struct tm *tm = m->utc ? gmtime(&t) : localtime(&t);
struct tm *tm = localtime(&t);
char date_str[1024];
strftime(date_str, sizeof(date_str), m->date_format, tm);
@ -58,7 +55,8 @@ content(struct module *mod)
strftime(time_str, sizeof(time_str), m->time_format, tm);
struct tag_set tags = {
.tags = (struct tag *[]){tag_new_string(mod, "time", time_str), tag_new_string(mod, "date", date_str)},
.tags = (struct tag *[]){tag_new_string(mod, "time", time_str),
tag_new_string(mod, "date", date_str)},
.count = 2,
};
@ -68,6 +66,7 @@ content(struct module *mod)
return exposable;
}
#include <pthread.h>
static int
run(struct module *mod)
{
@ -75,8 +74,6 @@ run(struct module *mod)
const struct bar *bar = mod->bar;
bar->refresh(bar);
int ret = 1;
while (true) {
struct timespec _now;
clock_gettime(CLOCK_REALTIME, &_now);
@ -90,12 +87,15 @@ run(struct module *mod)
switch (m->update_granularity) {
case UPDATE_GRANULARITY_SECONDS: {
const struct timeval next_second = {.tv_sec = now.tv_sec + 1, .tv_usec = 0};
const struct timeval next_second = {
.tv_sec = now.tv_sec + 1,
.tv_usec = 0};
struct timeval _timeout;
timersub(&next_second, &now, &_timeout);
assert(_timeout.tv_sec == 0 || (_timeout.tv_sec == 1 && _timeout.tv_usec == 0));
assert(_timeout.tv_sec == 0 ||
(_timeout.tv_sec == 1 && _timeout.tv_usec == 0));
timeout_ms = _timeout.tv_usec / 1000;
break;
}
@ -115,44 +115,44 @@ run(struct module *mod)
/* Add 1ms to account for rounding errors */
timeout_ms++;
LOG_DBG("now: %lds %ldµs -> timeout: %dms", now.tv_sec, now.tv_usec, timeout_ms);
LOG_DBG("now: %lds %ldµs -> timeout: %dms",
now.tv_sec, now.tv_usec, timeout_ms);
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
if (poll(fds, 1, timeout_ms) < 0) {
if (errno == EINTR)
continue;
poll(fds, 1, timeout_ms);
LOG_ERRNO("failed to poll");
if (fds[0].revents & POLLIN)
break;
}
if (fds[0].revents & POLLIN) {
ret = 0;
break;
}
bar->refresh(bar);
}
return ret;
return 0;
}
static struct module *
clock_new(struct particle *label, const char *date_format, const char *time_format, bool utc)
clock_new(struct particle *label, const char *date_format, const char *time_format)
{
struct private *m = calloc(1, sizeof(*m));
m->label = label;
m->date_format = strdup(date_format);
m->time_format = strdup(time_format);
m->utc = utc;
static const char *const seconds_formatters[] = {
"%c", "%s", "%S", "%T", "%r", "%X",
"%c",
"%s",
"%S",
"%T",
"%r",
"%X",
};
m->update_granularity = UPDATE_GRANULARITY_MINUTES;
for (size_t i = 0; i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]); i++) {
for (size_t i = 0;
i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]);
i++)
{
if (strstr(time_format, seconds_formatters[i]) != NULL) {
m->update_granularity = UPDATE_GRANULARITY_SECONDS;
break;
@ -160,7 +160,8 @@ clock_new(struct particle *label, const char *date_format, const char *time_form
}
LOG_DBG("using %s update granularity",
(m->update_granularity == UPDATE_GRANULARITY_MINUTES ? "minutes" : "seconds"));
(m->update_granularity == UPDATE_GRANULARITY_MINUTES
? "minutes" : "seconds"));
struct module *mod = module_common_new();
mod->private = m;
@ -177,11 +178,11 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *c = yml_get_value(node, "content");
const struct yml_node *date_format = yml_get_value(node, "date-format");
const struct yml_node *time_format = yml_get_value(node, "time-format");
const struct yml_node *utc = yml_get_value(node, "utc");
return clock_new(conf_to_particle(c, inherited), date_format != NULL ? yml_value_as_string(date_format) : "%x",
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M",
utc != NULL ? yml_value_as_bool(utc) : false);
return clock_new(
conf_to_particle(c, inherited),
date_format != NULL ? yml_value_as_string(date_format) : "%x",
time_format != NULL ? yml_value_as_string(time_format) : "%H:%M");
}
static bool
@ -190,7 +191,6 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
static const struct attr_info attrs[] = {
{"date-format", false, &conf_verify_string},
{"time-format", false, &conf_verify_string},
{"utc", false, &conf_verify_bool},
MODULE_COMMON_ATTRS,
};

View file

@ -1,297 +0,0 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#define LOG_MODULE "cpu"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
static const long min_poll_interval = 250;
struct cpu_stats {
uint32_t *prev_cores_idle;
uint32_t *prev_cores_nidle;
uint32_t *cur_cores_idle;
uint32_t *cur_cores_nidle;
};
struct private
{
struct particle *template;
uint16_t interval;
size_t core_count;
struct cpu_stats cpu_stats;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->template->destroy(m->template);
free(m->cpu_stats.prev_cores_idle);
free(m->cpu_stats.prev_cores_nidle);
free(m->cpu_stats.cur_cores_idle);
free(m->cpu_stats.cur_cores_nidle);
free(m);
module_default_destroy(mod);
}
static const char *
description(const struct module *mod)
{
return "cpu";
}
static uint32_t
get_cpu_nb_cores()
{
int nb_cores = sysconf(_SC_NPROCESSORS_ONLN);
LOG_DBG("CPU count: %d", nb_cores);
return nb_cores;
}
static bool
parse_proc_stat_line(const char *line, uint32_t *user, uint32_t *nice, uint32_t *system, uint32_t *idle,
uint32_t *iowait, uint32_t *irq, uint32_t *softirq, uint32_t *steal, uint32_t *guest,
uint32_t *guestnice)
{
int32_t core_id;
if (line[sizeof("cpu") - 1] == ' ') {
int read = sscanf(line,
"cpu %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
" %" SCNu32 " %" SCNu32 " %" SCNu32,
user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice);
return read == 10;
} else {
int read = sscanf(line,
"cpu%" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32
" %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32,
&core_id, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice);
return read == 11;
}
}
static uint8_t
get_cpu_usage_percent(const struct cpu_stats *cpu_stats, int8_t core_idx)
{
uint32_t prev_total = cpu_stats->prev_cores_idle[core_idx + 1] + cpu_stats->prev_cores_nidle[core_idx + 1];
uint32_t cur_total = cpu_stats->cur_cores_idle[core_idx + 1] + cpu_stats->cur_cores_nidle[core_idx + 1];
double totald = cur_total - prev_total;
double nidled = cpu_stats->cur_cores_nidle[core_idx + 1] - cpu_stats->prev_cores_nidle[core_idx + 1];
double percent = (nidled * 100) / (totald + 1);
return round(percent);
}
static void
refresh_cpu_stats(struct cpu_stats *cpu_stats, size_t core_count)
{
int32_t core = 0;
uint32_t user = 0;
uint32_t nice = 0;
uint32_t system = 0;
uint32_t idle = 0;
uint32_t iowait = 0;
uint32_t irq = 0;
uint32_t softirq = 0;
uint32_t steal = 0;
uint32_t guest = 0;
uint32_t guestnice = 0;
FILE *fp = NULL;
char *line = NULL;
size_t len = 0;
ssize_t read;
fp = fopen("/proc/stat", "re");
if (NULL == fp) {
LOG_ERRNO("unable to open /proc/stat");
return;
}
while ((read = getline(&line, &len, fp)) != -1 && core <= core_count) {
if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) {
if (!parse_proc_stat_line(line, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest,
&guestnice)) {
LOG_ERR("unable to parse /proc/stat line");
goto exit;
}
cpu_stats->prev_cores_idle[core] = cpu_stats->cur_cores_idle[core];
cpu_stats->prev_cores_nidle[core] = cpu_stats->cur_cores_nidle[core];
cpu_stats->cur_cores_idle[core] = idle + iowait;
cpu_stats->cur_cores_nidle[core] = user + nice + system + irq + softirq + steal;
core++;
}
}
exit:
fclose(fp);
free(line);
}
static struct exposable *
content(struct module *mod)
{
const struct private *m = mod->private;
mtx_lock(&mod->lock);
const size_t list_count = m->core_count + 1;
struct exposable *parts[list_count];
{
uint8_t total_usage = get_cpu_usage_percent(&m->cpu_stats, -1);
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_int(mod, "id", -1),
tag_new_int_range(mod, "cpu", total_usage, 0, 100),
},
.count = 2,
};
parts[0] = m->template->instantiate(m->template, &tags);
tag_set_destroy(&tags);
}
for (size_t i = 0; i < m->core_count; i++) {
uint8_t core_usage = get_cpu_usage_percent(&m->cpu_stats, i);
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_int(mod, "id", i),
tag_new_int_range(mod, "cpu", core_usage, 0, 100),
},
.count = 2,
};
parts[i + 1] = m->template->instantiate(m->template, &tags);
tag_set_destroy(&tags);
}
mtx_unlock(&mod->lock);
return dynlist_exposable_new(parts, list_count, 0, 0);
}
static int
run(struct module *mod)
{
const struct bar *bar = mod->bar;
bar->refresh(bar);
struct private *p = mod->private;
while (true) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval);
if (res < 0) {
if (EINTR == errno)
continue;
LOG_ERRNO("unable to poll abort fd");
return -1;
}
if (fds[0].revents & POLLIN)
break;
mtx_lock(&mod->lock);
refresh_cpu_stats(&p->cpu_stats, p->core_count);
mtx_unlock(&mod->lock);
bar->refresh(bar);
}
return 0;
}
static struct module *
cpu_new(uint16_t interval, struct particle *template)
{
uint32_t nb_cores = get_cpu_nb_cores();
struct private *p = calloc(1, sizeof(*p));
p->template = template;
p->interval = interval;
p->core_count = nb_cores;
p->cpu_stats.prev_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_nidle));
p->cpu_stats.prev_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_idle));
p->cpu_stats.cur_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_nidle));
p->cpu_stats.cur_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_idle));
struct module *mod = module_common_new();
mod->private = p;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
mod->description = &description;
return mod;
}
static struct module *
from_conf(const struct yml_node *node, struct conf_inherit inherited)
{
const struct yml_node *interval = yml_get_value(node, "poll-interval");
const struct yml_node *c = yml_get_value(node, "content");
return cpu_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited));
}
static bool
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
{
if (!conf_verify_unsigned(chain, node))
return false;
if (yml_value_as_int(node) < min_poll_interval) {
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
return false;
}
return true;
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"poll-interval", false, &conf_verify_poll_interval},
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_cpu_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_cpu_iface")));
#endif

View file

@ -1,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 <string.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>
@ -11,8 +11,8 @@
#define LOG_MODULE "foreign-toplevel"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
#include "../particles/dynlist.h"
#include "wlr-foreign-toplevel-management-unstable-v1.h"
#include "xdg-output-unstable-v1.h"
@ -46,8 +46,7 @@ struct toplevel {
tll(const struct output *) outputs;
};
struct private
{
struct private {
struct particle *template;
uint32_t manager_wl_name;
struct zwlr_foreign_toplevel_manager_v1 *manager;
@ -93,7 +92,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "toplevel";
}
@ -111,8 +110,7 @@ content(struct module *mod)
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;
bool show = false;
@ -120,10 +118,11 @@ content(struct module *mod)
if (m->all_monitors)
show = true;
else if (current_output != NULL) {
tll_foreach(top->outputs, it2)
{
tll_foreach(top->outputs, it2) {
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;
break;
}
@ -159,18 +158,22 @@ verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
if (version >= wanted)
return true;
LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version);
LOG_ERR("%s: need interface version %u, but compositor only implements %u",
iface, wanted, version);
return false;
}
static void
xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y)
xdg_output_handle_logical_position(void *data,
struct zxdg_output_v1 *xdg_output,
int32_t x, int32_t y)
{
}
static void
xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height)
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
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 module *mod = output->mod;
@ -194,7 +198,8 @@ xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char
}
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
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 module *mod = top->mod;
@ -242,8 +248,7 @@ output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
mtx_lock(&mod->lock);
const struct output *output = NULL;
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
if (it->item.wl_output == wl_output) {
output = &it->item;
break;
@ -255,8 +260,7 @@ output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
goto out;
}
tll_foreach(top->outputs, it)
{
tll_foreach(top->outputs, it) {
if (it->item == output) {
LOG_ERR("output-enter event on output we're already on");
goto out;
@ -271,7 +275,8 @@ out:
}
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 module *mod = top->mod;
@ -280,8 +285,7 @@ output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
mtx_lock(&mod->lock);
const struct output *output = NULL;
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
if (it->item.wl_output == wl_output) {
output = &it->item;
break;
@ -294,10 +298,10 @@ output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct
}
bool output_removed = false;
tll_foreach(top->outputs, it)
{
tll_foreach(top->outputs, it) {
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);
output_removed = true;
break;
@ -314,7 +318,8 @@ out:
}
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;
@ -324,21 +329,12 @@ state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_arra
bool fullscreen = false;
enum zwlr_foreign_toplevel_handle_v1_state *state;
wl_array_for_each(state, states)
{
wl_array_for_each(state, states) {
switch (*state) {
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED:
maximized = true;
break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED:
minimized = true;
break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED:
activated = true;
break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN:
fullscreen = true;
break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: maximized = true; break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: minimized = true; break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: activated = true; break;
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: fullscreen = true; break;
}
}
@ -368,8 +364,7 @@ closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
struct private *m = mod->private;
mtx_lock(&mod->lock);
tll_foreach(m->toplevels, it)
{
tll_foreach(m->toplevels, it) {
if (it->item.handle == handle) {
toplevel_free(top);
tll_remove(m->toplevels, it);
@ -377,13 +372,12 @@ closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle)
}
}
mtx_unlock(&mod->lock);
const struct bar *bar = mod->bar;
bar->refresh(bar);
}
static void
parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent)
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
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 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);
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);
}
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 private *m = mod->private;
@ -444,12 +442,15 @@ output_xdg_output(struct output *output)
if (output->xdg_output != NULL)
return;
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(m->xdg_output_manager, output->wl_output);
zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output);
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
m->xdg_output_manager, output->wl_output);
zxdg_output_v1_add_listener(
output->xdg_output, &xdg_output_listener, output);
}
static void
handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
struct module *mod = data;
struct private *m = mod->private;
@ -469,7 +470,8 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
struct output output = {
.mod = mod,
.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);
@ -483,10 +485,12 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
if (!verify_iface_version(interface, version, required))
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);
tll_foreach(m->outputs, it) output_xdg_output(&it->item);
tll_foreach(m->outputs, it)
output_xdg_output(&it->item);
mtx_unlock(&mod->lock);
}
}
@ -499,19 +503,16 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
mtx_lock(&mod->lock);
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
const struct output *output = &it->item;
if (output->wl_name == name) {
/* Loop all toplevels */
tll_foreach(m->toplevels, it2)
{
tll_foreach(m->toplevels, it2) {
/* And remove this output from their list of tracked
* outputs */
tll_foreach(it2->item.outputs, it3)
{
tll_foreach(it2->item.outputs, it3) {
if (it3->item == output) {
tll_remove(it2->item.outputs, it3);
break;
@ -547,8 +548,9 @@ run(struct module *mod)
goto out;
}
if ((registry = wl_display_get_registry(display)) == NULL
|| wl_registry_add_listener(registry, &registry_listener, mod) != 0) {
if ((registry = wl_display_get_registry(display)) == NULL ||
wl_registry_add_listener(registry, &registry_listener, mod) != 0)
{
LOG_ERR("failed to get Wayland registry");
goto out;
}
@ -556,14 +558,18 @@ run(struct module *mod)
wl_display_roundtrip(display);
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;
}
m->manager = wl_registry_bind(registry, m->manager_wl_name, &zwlr_foreign_toplevel_manager_v1_interface,
required_manager_interface_version);
m->manager = wl_registry_bind(
registry, m->manager_wl_name,
&zwlr_foreign_toplevel_manager_v1_interface,
required_manager_interface_version);
zwlr_foreign_toplevel_manager_v1_add_listener(m->manager, &manager_listener, mod);
zwlr_foreign_toplevel_manager_v1_add_listener(
m->manager, &manager_listener, mod);
while (true) {
wl_display_flush(display);
@ -597,14 +603,12 @@ run(struct module *mod)
}
out:
tll_foreach(m->toplevels, it)
{
tll_foreach(m->toplevels, it) {
toplevel_free(&it->item);
tll_remove(m->toplevels, it);
}
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
output_free(&it->item);
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 *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

View file

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

View file

@ -2,8 +2,8 @@
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <json-c/json_util.h>
@ -11,7 +11,7 @@
bool i3_get_socket_address(struct sockaddr_un *addr);
bool i3_send_pkg(int sock, int cmd, char *data);
typedef bool (*i3_ipc_callback_t)(int sock, int type, const struct json_object *json, void *data);
typedef bool (*i3_ipc_callback_t)(int type, const struct json_object *json, void *data);
struct i3_ipc_callbacks {
void (*burst_done)(void *data);
@ -43,4 +43,6 @@ struct i3_ipc_callbacks {
i3_ipc_callback_t event_input;
};
bool i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *callbacks, void *data);
bool i3_receive_loop(
int abort_fd, int sock,
const struct i3_ipc_callbacks *callbacks, void *data);

View file

@ -1,27 +1,27 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <assert.h>
#include <threads.h>
#include <fcntl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <tllist.h>
#define LOG_MODULE "i3"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
#include "i3-common.h"
#include "i3-ipc.h"
#include "i3-common.h"
enum sort_mode { SORT_NONE, SORT_NATIVE, SORT_ASCENDING, SORT_DESCENDING };
enum sort_mode {SORT_NONE, SORT_ASCENDING, SORT_DESCENDING};
struct ws_content {
char *name;
@ -29,7 +29,6 @@ struct ws_content {
};
struct workspace {
int id;
char *name;
int name_as_int; /* -1 if name is not a decimal number */
bool persistent;
@ -38,7 +37,6 @@ struct workspace {
bool visible;
bool focused;
bool urgent;
bool empty;
struct {
unsigned id;
@ -48,8 +46,7 @@ struct workspace {
} window;
};
struct private
{
struct private {
int left_spacing;
int right_spacing;
@ -62,7 +59,6 @@ struct private
size_t count;
} ws_content;
bool strip_workspace_numbers;
enum sort_mode sort_mode;
tll(struct workspace) workspaces;
@ -74,22 +70,6 @@ static int
workspace_name_as_int(const char *name)
{
int name_as_int = 0;
/* First check for N:name pattern (set $ws1 “1:foobar”) */
const char *colon = strchr(name, ':');
if (colon != NULL) {
for (const char *p = name; p < colon; p++) {
if (!(*p >= '0' && *p < '9'))
return -1;
name_as_int *= 10;
name_as_int += *p - '0';
}
return name_as_int;
}
/* Then, if the name is a number *only* (set $ws1 1) */
for (const char *p = name; *p != '\0'; p++) {
if (!(*p >= '0' && *p <= '9'))
return -1;
@ -105,18 +85,14 @@ static bool
workspace_from_json(const struct json_object *json, struct workspace *ws)
{
/* Always present */
struct json_object *id, *name, *output;
if (!json_object_object_get_ex(json, "id", &id) || !json_object_object_get_ex(json, "name", &name)
|| !json_object_object_get_ex(json, "output", &output)) {
LOG_ERR("workspace reply/event without 'name' and/or 'output' "
"properties");
struct json_object *name, *output;
if (!json_object_object_get_ex(json, "name", &name) ||
!json_object_object_get_ex(json, "output", &output))
{
LOG_ERR("workspace reply/event without 'name' and/or 'output' property");
return false;
}
/* Sway only */
struct json_object *focus = NULL;
json_object_object_get_ex(json, "focus", &focus);
/* Optional */
struct json_object *visible = NULL, *focused = NULL, *urgent = NULL;
json_object_object_get_ex(json, "visible", &visible);
@ -125,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 size_t node_count = focus != NULL ? json_object_array_length(focus) : 0;
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),
*ws = (struct workspace) {
.name = strdup(name_as_string),
.name_as_int = name_as_int,
.name_as_int = workspace_name_as_int(name_as_string),
.persistent = false,
.output = strdup(json_object_get_string(output)),
.visible = json_object_get_boolean(visible),
.focused = json_object_get_boolean(focused),
.urgent = json_object_get_boolean(urgent),
.empty = is_empty && json_object_get_boolean(focused),
.window = {.title = NULL, .pid = -1},
};
return true;
}
static void
workspace_free_persistent(struct workspace *ws)
{
free(ws->output);
ws->output = NULL;
free(ws->window.title);
ws->window.title = NULL;
free(ws->window.application);
ws->window.application = NULL;
ws->id = -1;
}
static void
workspace_free(struct workspace *ws)
{
workspace_free_persistent(ws);
free(ws->name);
ws->name = NULL;
free(ws->name); ws->name = NULL;
free(ws->output); ws->output = NULL;
free(ws->window.title); ws->window.title = NULL;
free(ws->window.application); ws->window.application = NULL;
}
static void
workspaces_free(struct private *m, bool free_persistent)
{
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
if (free_persistent || !it->item.persistent) {
workspace_free(&it->item);
tll_remove(m->workspaces, it);
@ -178,6 +135,7 @@ workspaces_free(struct private *m, bool free_persistent)
}
}
static void
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);
return;
case SORT_NATIVE:
if (ws.name_as_int >= 0) {
tll_foreach(m->workspaces, it)
{
if (it->item.name_as_int < 0)
continue;
if (it->item.name_as_int > ws.name_as_int) {
tll_insert_before(m->workspaces, it, ws);
return;
}
}
};
tll_push_back(m->workspaces, ws);
return;
case SORT_ASCENDING:
if (ws.name_as_int >= 0) {
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
if (it->item.name_as_int < 0)
continue;
if (it->item.name_as_int > ws.name_as_int) {
@ -214,9 +155,10 @@ workspace_add(struct private *m, struct workspace ws)
}
}
} else {
tll_foreach(m->workspaces, it)
{
if (strcoll(it->item.name, ws.name) > 0 || it->item.name_as_int >= 0) {
tll_foreach(m->workspaces, it) {
if (strcoll(it->item.name, ws.name) > 0 ||
it->item.name_as_int >= 0)
{
tll_insert_before(m->workspaces, it, ws);
return;
}
@ -227,16 +169,14 @@ workspace_add(struct private *m, struct workspace ws)
case SORT_DESCENDING:
if (ws.name_as_int >= 0) {
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
if (it->item.name_as_int < ws.name_as_int) {
tll_insert_before(m->workspaces, it, ws);
return;
}
}
} else {
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
if (it->item.name_as_int >= 0)
continue;
if (strcoll(it->item.name, ws.name) < 0) {
@ -251,13 +191,12 @@ workspace_add(struct private *m, struct workspace ws)
}
static void
workspace_del(struct private *m, int id)
workspace_del(struct private *m, const char *name)
{
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
struct workspace *ws = &it->item;
if (ws->id != id)
if (strcmp(ws->name, name) != 0)
continue;
workspace_free(ws);
@ -267,22 +206,9 @@ workspace_del(struct private *m, int id)
}
static struct workspace *
workspace_lookup(struct private *m, int id)
workspace_lookup(struct private *m, const char *name)
{
tll_foreach(m->workspaces, it)
{
struct workspace *ws = &it->item;
if (ws->id == id)
return ws;
}
return NULL;
}
static struct workspace *
workspace_lookup_by_name(struct private *m, const char *name)
{
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
struct workspace *ws = &it->item;
if (strcmp(ws->name, name) == 0)
return ws;
@ -291,7 +217,7 @@ workspace_lookup_by_name(struct private *m, const char *name)
}
static bool
handle_get_version_reply(int sock, int type, const struct json_object *json, void *_m)
handle_get_version_reply(int type, const struct json_object *json, void *_m)
{
struct json_object *version;
if (!json_object_object_get_ex(json, "human_readable", &version)) {
@ -304,7 +230,7 @@ handle_get_version_reply(int sock, int type, const struct json_object *json, voi
}
static bool
handle_subscribe_reply(int sock, int type, const struct json_object *json, void *_m)
handle_subscribe_reply(int type, const struct json_object *json, void *_m)
{
struct json_object *success;
if (!json_object_object_get_ex(json, "success", &success)) {
@ -323,37 +249,12 @@ handle_subscribe_reply(int sock, int type, const struct json_object *json, void
static bool
workspace_update_or_add(struct private *m, const struct json_object *ws_json)
{
struct json_object *_id;
if (!json_object_object_get_ex(ws_json, "id", &_id))
struct json_object *name;
if (!json_object_object_get_ex(ws_json, "name", &name))
return false;
const int id = json_object_get_int(_id);
struct workspace *already_exists = workspace_lookup(m, id);
if (already_exists == NULL) {
/*
* No workspace with this ID.
*
* Try looking it up again, but this time using the name. If
* we get a match, check if its an empty, persistent
* workspace, and if so, use it.
*
* This is necessary, since empty, persistent workspaces dont
* exist in the i3/Sway server, and thus we dont _have_ an
* ID.
*/
struct json_object *_name;
if (json_object_object_get_ex(ws_json, "name", &_name)) {
const char *name = json_object_get_string(_name);
if (name != NULL) {
struct workspace *maybe_persistent = workspace_lookup_by_name(m, name);
if (maybe_persistent != NULL && maybe_persistent->persistent && maybe_persistent->id < 0) {
already_exists = maybe_persistent;
}
}
}
}
const char *name_as_string = json_object_get_string(name);
struct workspace *already_exists = workspace_lookup(m, name_as_string);
if (already_exists != NULL) {
bool persistent = already_exists->persistent;
@ -375,7 +276,7 @@ workspace_update_or_add(struct private *m, const struct json_object *ws_json)
}
static bool
handle_get_workspaces_reply(int sock, int type, const struct json_object *json, void *_mod)
handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -402,7 +303,7 @@ err:
}
static bool
handle_workspace_event(int sock, int type, const struct json_object *json, void *_mod)
handle_workspace_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -418,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_empty = strcmp(change_str, "empty") == 0;
bool is_focused = strcmp(change_str, "focus") == 0;
bool is_rename = strcmp(change_str, "rename") == 0;
bool is_move = strcmp(change_str, "move") == 0;
bool is_urgent = strcmp(change_str, "urgent") == 0;
bool is_reload = strcmp(change_str, "reload") == 0;
struct json_object *current, *_current_id;
if ((!json_object_object_get_ex(json, "current", &current)
|| !json_object_object_get_ex(current, "id", &_current_id))
&& !is_reload) {
LOG_ERR("workspace event without 'current' and/or 'id' properties");
if (is_reload) {
LOG_WARN("unimplemented: 'reload' event");
return true;
}
struct json_object *current, *_current_name;
if (!json_object_object_get_ex(json, "current", &current) ||
!json_object_object_get_ex(current, "name", &_current_name))
{
LOG_ERR("workspace event without 'current' and/or 'name' properties");
return false;
}
int current_id = json_object_get_int(_current_id);
const char *current_name = json_object_get_string(_current_name);
mtx_lock(&mod->lock);
@ -441,34 +345,36 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
}
else if (is_empty) {
struct workspace *ws = workspace_lookup(m, current_id);
struct workspace *ws = workspace_lookup(m, current_name);
assert(ws != NULL);
if (!ws->persistent)
workspace_del(m, current_id);
workspace_del(m, current_name);
else {
workspace_free_persistent(ws);
ws->empty = true;
workspace_free(ws);
ws->name = strdup(current_name);
assert(ws->persistent);
}
}
else if (is_focused) {
struct json_object *old, *_old_id, *urgent;
if (!json_object_object_get_ex(json, "old", &old) || !json_object_object_get_ex(old, "id", &_old_id)
|| !json_object_object_get_ex(current, "urgent", &urgent)) {
struct json_object *old, *_old_name, *urgent;
if (!json_object_object_get_ex(json, "old", &old) ||
!json_object_object_get_ex(old, "name", &_old_name) ||
!json_object_object_get_ex(current, "urgent", &urgent))
{
LOG_ERR("workspace 'focused' event without 'old', 'name' and/or 'urgent' property");
mtx_unlock(&mod->lock);
return false;
}
struct workspace *w = workspace_lookup(m, current_id);
struct workspace *w = workspace_lookup(m, current_name);
assert(w != NULL);
LOG_DBG("w: %s", w->name);
/* Mark all workspaces on current's output invisible */
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
struct workspace *ws = &it->item;
if (ws->output != NULL && strcmp(ws->output, w->output) == 0)
ws->visible = false;
@ -479,67 +385,12 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
w->visible = true;
/* Old workspace is no longer focused */
int old_id = json_object_get_int(_old_id);
struct workspace *old_w = workspace_lookup(m, old_id);
const char *old_name = json_object_get_string(_old_name);
struct workspace *old_w = workspace_lookup(m, old_name);
if (old_w != NULL)
old_w->focused = false;
}
else if (is_rename) {
struct workspace *w = workspace_lookup(m, current_id);
assert(w != NULL);
struct json_object *_current_name;
if (!json_object_object_get_ex(current, "name", &_current_name)) {
LOG_ERR("workspace 'rename' event without 'name' property");
mtx_unlock(&mod->lock);
return false;
}
free(w->name);
w->name = strdup(json_object_get_string(_current_name));
w->name_as_int = workspace_name_as_int(w->name);
/* Re-add the workspace to ensure correct sorting */
struct workspace ws = *w;
tll_foreach(m->workspaces, it)
{
if (it->item.id == current_id) {
tll_remove(m->workspaces, it);
break;
}
}
workspace_add(m, ws);
}
else if (is_move) {
struct workspace *w = workspace_lookup(m, current_id);
struct json_object *_current_output;
if (!json_object_object_get_ex(current, "output", &_current_output)) {
LOG_ERR("workspace 'move' event without 'output' property");
mtx_unlock(&mod->lock);
return false;
}
const char *current_output_string = json_object_get_string(_current_output);
/* Ignore fallback_output ("For when there's no connected outputs") */
if (strcmp(current_output_string, "FALLBACK") != 0) {
assert(w != NULL);
free(w->output);
w->output = strdup(current_output_string);
/*
* If the moved workspace was focused, schedule a full update because
* visibility for other workspaces may have changed.
*/
if (w->focused) {
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
}
}
else if (is_urgent) {
struct json_object *urgent;
if (!json_object_object_get_ex(current, "urgent", &urgent)) {
@ -548,20 +399,10 @@ handle_workspace_event(int sock, int type, const struct json_object *json, void
return false;
}
struct workspace *w = workspace_lookup(m, current_id);
struct workspace *w = workspace_lookup(m, current_name);
w->urgent = json_object_get_boolean(urgent);
}
else if (is_reload) {
/* Schedule full update to check if anything was changed
* during reload */
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
else {
LOG_WARN("unimplemented workspace event '%s'", change_str);
}
m->dirty = true;
mtx_unlock(&mod->lock);
return true;
@ -572,7 +413,7 @@ err:
}
static bool
handle_window_event(int sock, int type, const struct json_object *json, void *_mod)
handle_window_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -595,8 +436,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
struct workspace *ws = NULL;
size_t focused = 0;
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
if (it->item.focused) {
ws = &it->item;
focused++;
@ -606,20 +446,6 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
assert(focused == 1);
assert(ws != NULL);
struct json_object *container, *id, *name;
if (!json_object_object_get_ex(json, "container", &container) || !json_object_object_get_ex(container, "id", &id)
|| !json_object_object_get_ex(container, "name", &name)) {
mtx_unlock(&mod->lock);
LOG_ERR("window event without 'container' with 'id' and 'name'");
return false;
}
if ((is_close || is_title) && ws->window.id != json_object_get_int(id)) {
/* Ignore close event and title changed event if it's not current window */
mtx_unlock(&mod->lock);
return true;
}
if (is_close) {
free(ws->window.title);
free(ws->window.application);
@ -631,6 +457,23 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
m->dirty = true;
mtx_unlock(&mod->lock);
return true;
}
struct json_object *container, *id, *name;
if (!json_object_object_get_ex(json, "container", &container) ||
!json_object_object_get_ex(container, "id", &id) ||
!json_object_object_get_ex(container, "name", &name))
{
mtx_unlock(&mod->lock);
LOG_ERR("window event without 'container' with 'id' and 'name'");
return false;
}
if (is_title && ws->window.id != json_object_get_int(id)) {
/* Ignore title changed event if it's not current window */
mtx_unlock(&mod->lock);
return true;
}
free(ws->window.title);
@ -651,24 +494,27 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
struct json_object *app_id;
struct json_object *pid;
if (json_object_object_get_ex(container, "app_id", &app_id) && json_object_get_string(app_id) != NULL) {
if (json_object_object_get_ex(container, "app_id", &app_id) &&
json_object_get_string(app_id) != NULL)
{
free(ws->window.application);
ws->window.application = strdup(json_object_get_string(app_id));
LOG_DBG("application: \"%s\", via 'app_id'", ws->window.application);
}
/* If PID has changed, update application name from /proc/<pid>/comm */
else if (json_object_object_get_ex(container, "pid", &pid) && ws->window.pid != json_object_get_int(pid)) {
else if (json_object_object_get_ex(container, "pid", &pid) &&
ws->window.pid != json_object_get_int(pid))
{
ws->window.pid = json_object_get_int(pid);
char path[64];
snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid);
int fd = open(path, O_RDONLY | O_CLOEXEC);
int fd = open(path, O_RDONLY);
if (fd == -1) {
/* Application may simply have terminated */
free(ws->window.application);
ws->window.application = NULL;
free(ws->window.application); ws->window.application = NULL;
ws->window.pid = -1;
m->dirty = true;
@ -693,7 +539,7 @@ handle_window_event(int sock, int type, const struct json_object *json, void *_m
}
static bool
handle_mode_event(int sock, int type, const struct json_object *json, void *_mod)
handle_mode_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -735,7 +581,7 @@ run(struct module *mod)
if (!i3_get_socket_address(&addr))
return 1;
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
LOG_ERRNO("failed to create UNIX socket");
return 1;
@ -752,19 +598,10 @@ run(struct module *mod)
for (size_t i = 0; i < m->persistent_count; i++) {
const char *name_as_string = m->persistent_workspaces[i];
int name_as_int = workspace_name_as_int(name_as_string);
if (m->strip_workspace_numbers) {
const char *colon = strchr(name_as_string, ':');
if (colon != NULL)
name_as_string = colon++;
}
struct workspace ws = {
.id = -1,
.name = strdup(name_as_string),
.name_as_int = name_as_int,
.name_as_int = workspace_name_as_int(name_as_string),
.persistent = true,
.empty = true,
};
workspace_add(m, ws);
}
@ -824,7 +661,7 @@ ws_content_for_name(struct private *m, const char *name)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "i3/sway";
}
@ -840,47 +677,30 @@ content(struct module *mod)
struct exposable *particles[tll_length(m->workspaces) + 1];
struct exposable *current = NULL;
tll_foreach(m->workspaces, it)
{
tll_foreach(m->workspaces, it) {
struct workspace *ws = &it->item;
const struct ws_content *template = NULL;
/* Lookup content template for workspace. Fall back to default
* template if this workspace doesn't have a specific
* template */
if (ws->name == NULL) {
LOG_ERR("%d %d", ws->name_as_int, ws->id);
}
template = ws_content_for_name(m, ws->name);
if (template == NULL) {
LOG_DBG("no ws template for %s, using default template", ws->name);
template = ws_content_for_name(m, "");
}
const char *state = ws->urgent ? "urgent" : ws->visible ? ws->focused ? "focused" : "unfocused" : "invisible";
LOG_DBG("name=%s (name-as-int=%d): visible=%s, focused=%s, urgent=%s, empty=%s, state=%s, "
"application=%s, title=%s, mode=%s",
ws->name, ws->name_as_int, ws->visible ? "yes" : "no", ws->focused ? "yes" : "no",
ws->urgent ? "yes" : "no", ws->empty ? "yes" : "no", state, ws->window.application, ws->window.title,
m->mode);
const char *name = ws->name;
if (m->strip_workspace_numbers) {
const char *colon = strchr(name, ':');
if (colon != NULL)
name = colon + 1;
}
const char *state =
ws->urgent ? "urgent" :
ws->visible ? ws->focused ? "focused" : "unfocused" :
"invisible";
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "name", name),
tag_new_string(mod, "output", ws->output),
tag_new_string(mod, "name", ws->name),
tag_new_bool(mod, "visible", ws->visible),
tag_new_bool(mod, "focused", ws->focused),
tag_new_bool(mod, "urgent", ws->urgent),
tag_new_bool(mod, "empty", ws->empty),
tag_new_string(mod, "state", state),
tag_new_string(mod, "application", ws->window.application),
@ -888,7 +708,7 @@ content(struct module *mod)
tag_new_string(mod, "mode", m->mode),
},
.count = 10,
.count = 8,
};
if (ws->focused) {
@ -898,9 +718,12 @@ content(struct module *mod)
}
if (template == NULL) {
LOG_WARN("no ws template for %s, and no default template available", ws->name);
LOG_WARN(
"no ws template for %s, and no default template available",
ws->name);
} else {
particles[particle_count++] = template->content->instantiate(template->content, &tags);
particles[particle_count++] = template->content->instantiate(
template->content, &tags);
}
tag_set_destroy(&tags);
@ -910,7 +733,8 @@ content(struct module *mod)
particles[particle_count++] = current;
mtx_unlock(&mod->lock);
return dynlist_exposable_new(particles, particle_count, m->left_spacing, m->right_spacing);
return dynlist_exposable_new(
particles, particle_count, m->left_spacing, m->right_spacing);
}
/* Maps workspace name to a content particle. */
@ -920,9 +744,10 @@ struct i3_workspaces {
};
static struct module *
i3_new(struct i3_workspaces workspaces[], size_t workspace_count, int left_spacing, int right_spacing,
enum sort_mode sort_mode, size_t persistent_count, const char *persistent_workspaces[static persistent_count],
bool strip_workspace_numbers)
i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
int left_spacing, int right_spacing, enum sort_mode sort_mode,
size_t persistent_count,
const char *persistent_workspaces[static persistent_count])
{
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->strip_workspace_numbers = strip_workspace_numbers;
m->sort_mode = sort_mode;
m->persistent_count = persistent_count;
m->persistent_workspaces = calloc(persistent_count, sizeof(m->persistent_workspaces[0]));
m->persistent_workspaces = calloc(
persistent_count, sizeof(m->persistent_workspaces[0]));
for (size_t i = 0; i < persistent_count; i++)
m->persistent_workspaces[i] = strdup(persistent_workspaces[i]);
@ -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 *sort = yml_get_value(node, "sort");
const struct yml_node *persistent = yml_get_value(node, "persistent");
const struct yml_node *strip_workspace_number = yml_get_value(node, "strip-workspace-numbers");
int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
int right = spacing != NULL ? yml_value_as_int(spacing)
: right_spacing != NULL ? yml_value_as_int(right_spacing)
: 0;
int left = spacing != NULL ? yml_value_as_int(spacing) :
left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
int right = spacing != NULL ? yml_value_as_int(spacing) :
right_spacing != NULL ? yml_value_as_int(right_spacing) : 0;
const char *sort_value = sort != NULL ? yml_value_as_string(sort) : NULL;
enum sort_mode sort_mode = sort_value == NULL ? SORT_NONE
: strcmp(sort_value, "none") == 0 ? SORT_NONE
: strcmp(sort_value, "native") == 0 ? SORT_NATIVE
: strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING
: SORT_DESCENDING;
enum sort_mode sort_mode =
sort_value == NULL ? SORT_NONE :
strcmp(sort_value, "none") == 0 ? SORT_NONE :
strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING : SORT_DESCENDING;
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];
if (persistent != NULL) {
size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(persistent); it.node != NULL; yml_list_next(&it), idx++) {
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);
}
}
@ -992,27 +819,36 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
struct i3_workspaces workspaces[yml_dict_length(c)];
size_t idx = 0;
for (struct yml_dict_iter it = yml_dict_iter(c); it.key != NULL; yml_dict_next(&it), idx++) {
for (struct yml_dict_iter it = yml_dict_iter(c);
it.key != NULL;
yml_dict_next(&it), idx++)
{
workspaces[idx].name = yml_value_as_string(it.key);
workspaces[idx].content = conf_to_particle(it.value, inherited);
}
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode, persistent_count, persistent_workspaces,
(strip_workspace_number != NULL ? yml_value_as_bool(strip_workspace_number) : false));
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode,
persistent_count, persistent_workspaces);
}
static bool
verify_content(keychain_t *chain, const struct yml_node *node)
{
if (!yml_is_dict(node)) {
LOG_ERR("%s: must be a dictionary of workspace-name: particle mappings", conf_err_prefix(chain, node));
LOG_ERR(
"%s: must be a dictionary of workspace-name: particle mappings",
conf_err_prefix(chain, node));
return false;
}
for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) {
for (struct yml_dict_iter it = yml_dict_iter(node);
it.key != NULL;
yml_dict_next(&it))
{
const char *key = yml_value_as_string(it.key);
if (key == NULL) {
LOG_ERR("%s: key must be a string (a i3 workspace name)", conf_err_prefix(chain, it.key));
LOG_ERR("%s: key must be a string (a i3 workspace name)",
conf_err_prefix(chain, it.key));
return false;
}
@ -1028,7 +864,8 @@ verify_content(keychain_t *chain, const struct yml_node *node)
static bool
verify_sort(keychain_t *chain, const struct yml_node *node)
{
return conf_verify_enum(chain, node, (const char *[]){"none", "native", "ascending", "descending"}, 4);
return conf_verify_enum(
chain, node, (const char *[]){"none", "ascending", "descending"}, 3);
}
static bool
@ -1041,12 +878,11 @@ static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"spacing", false, &conf_verify_unsigned},
{"left-spacing", false, &conf_verify_unsigned},
{"right-spacing", false, &conf_verify_unsigned},
{"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_int},
{"sort", false, &verify_sort},
{"persistent", false, &verify_persistent},
{"strip-workspace-numbers", false, &conf_verify_bool},
{"content", true, &verify_content},
{"anchors", false, NULL},
{NULL, false, NULL},
@ -1061,5 +897,5 @@ const struct module_iface module_i3_iface = {
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_i3_iface")));
extern const struct module_iface iface __attribute__((weak, alias("module_i3_iface"))) ;
#endif

View file

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

View file

@ -1,200 +0,0 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#define LOG_MODULE "mem"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../plugin.h"
static const long min_poll_interval = 250;
struct private
{
struct particle *label;
uint16_t interval;
uint64_t mem_free;
uint64_t mem_total;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->label->destroy(m->label);
free(m);
module_default_destroy(mod);
}
static const char *
description(const struct module *mod)
{
return "mem";
}
static bool
get_mem_stats(uint64_t *mem_free, uint64_t *mem_total)
{
bool mem_total_found = false;
bool mem_free_found = false;
FILE *fp = NULL;
char *line = NULL;
size_t len = 0;
ssize_t read = 0;
fp = fopen("/proc/meminfo", "re");
if (NULL == fp) {
LOG_ERRNO("unable to open /proc/meminfo");
return false;
}
while ((read = getline(&line, &len, fp)) != -1) {
if (strncmp(line, "MemTotal:", sizeof("MemTotal:") - 1) == 0) {
read = sscanf(line + sizeof("MemTotal:") - 1, "%" SCNu64, mem_total);
mem_total_found = (read == 1);
}
if (strncmp(line, "MemAvailable:", sizeof("MemAvailable:") - 1) == 0) {
read = sscanf(line + sizeof("MemAvailable:"), "%" SCNu64, mem_free);
mem_free_found = (read == 1);
}
}
free(line);
fclose(fp);
return mem_free_found && mem_total_found;
}
static struct exposable *
content(struct module *mod)
{
const struct private *p = mod->private;
mtx_lock(&mod->lock);
const uint64_t mem_free = p->mem_free;
const uint64_t mem_total = p->mem_total;
const uint64_t mem_used = mem_total - mem_free;
double percent_used = ((double)mem_used * 100) / (mem_total + 1);
double percent_free = ((double)mem_free * 100) / (mem_total + 1);
struct tag_set tags = {
.tags = (struct tag *[]){tag_new_int(mod, "free", mem_free * 1024), tag_new_int(mod, "used", mem_used * 1024),
tag_new_int(mod, "total", mem_total * 1024),
tag_new_int_range(mod, "percent_free", round(percent_free), 0, 100),
tag_new_int_range(mod, "percent_used", round(percent_used), 0, 100)},
.count = 5,
};
struct exposable *exposable = p->label->instantiate(p->label, &tags);
tag_set_destroy(&tags);
mtx_unlock(&mod->lock);
return exposable;
}
static int
run(struct module *mod)
{
const struct bar *bar = mod->bar;
bar->refresh(bar);
struct private *p = mod->private;
while (true) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int res = poll(fds, 1, p->interval);
if (res < 0) {
if (EINTR == errno) {
continue;
}
LOG_ERRNO("unable to poll abort fd");
return -1;
}
if (fds[0].revents & POLLIN)
break;
mtx_lock(&mod->lock);
p->mem_free = 0;
p->mem_total = 0;
if (!get_mem_stats(&p->mem_free, &p->mem_total)) {
LOG_ERR("unable to retrieve the memory stats");
}
mtx_unlock(&mod->lock);
bar->refresh(bar);
}
return 0;
}
static struct module *
mem_new(uint16_t interval, struct particle *label)
{
struct private *p = calloc(1, sizeof(*p));
p->label = label;
p->interval = interval;
struct module *mod = module_common_new();
mod->private = p;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
mod->description = &description;
return mod;
}
static struct module *
from_conf(const struct yml_node *node, struct conf_inherit inherited)
{
const struct yml_node *interval = yml_get_value(node, "poll-interval");
const struct yml_node *c = yml_get_value(node, "content");
return mem_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited));
}
static bool
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
{
if (!conf_verify_unsigned(chain, node))
return false;
if (yml_value_as_int(node) < min_poll_interval) {
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
return false;
}
return true;
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"poll-interval", false, &conf_verify_poll_interval},
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_mem_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_mem_iface")));
#endif

View file

@ -2,148 +2,35 @@ module_sdk = declare_dependency(dependencies: [pixman, threads, tllist, fcft])
modules = []
# Optional deps
alsa = dependency('alsa', required: get_option('plugin-alsa'))
plugin_alsa_enabled = alsa.found()
udev_backlight = dependency('libudev', required: get_option('plugin-backlight'))
plugin_backlight_enabled = udev_backlight.found()
udev_battery = dependency('libudev', required: get_option('plugin-battery'))
plugin_battery_enabled = udev_battery.found()
plugin_clock_enabled = get_option('plugin-clock').allowed()
plugin_cpu_enabled = get_option('plugin-cpu').allowed()
plugin_disk_io_enabled = get_option('plugin-disk-io').allowed()
plugin_dwl_enabled = get_option('plugin-dwl').allowed()
plugin_foreign_toplevel_enabled = backend_wayland and get_option('plugin-foreign-toplevel').allowed()
plugin_mem_enabled = get_option('plugin-mem').allowed()
mpd = dependency('libmpdclient', required: get_option('plugin-mpd'))
plugin_mpd_enabled = mpd.found()
json_i3 = dependency('json-c', required: get_option('plugin-i3'))
plugin_i3_enabled = json_i3.found()
plugin_label_enabled = get_option('plugin-label').allowed()
plugin_network_enabled = get_option('plugin-network').allowed()
pipewire = dependency('libpipewire-0.3', required: get_option('plugin-pipewire'))
json_pipewire = dependency('json-c', required: get_option('plugin-pipewire'))
plugin_pipewire_enabled = pipewire.found() and json_pipewire.found()
pulse = dependency('libpulse', required: get_option('plugin-pulse'))
plugin_pulse_enabled = pulse.found()
udev_removables = dependency('libudev', required: get_option('plugin-removables'))
plugin_removables_enabled = udev_removables.found()
plugin_river_enabled = backend_wayland and get_option('plugin-river').allowed()
plugin_script_enabled = get_option('plugin-script').allowed()
json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb'))
plugin_sway_xkb_enabled = json_sway_xkb.found()
json_niri_language = dependency('json-c', required: get_option('plugin-niri-language'))
plugin_niri_language_enabled = json_niri_language.found()
json_niri_workspaces = dependency('json-c', required: get_option('plugin-niri-workspaces'))
plugin_niri_workspaces_enabled = json_niri_workspaces.found()
xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb'))
plugin_xkb_enabled = backend_x11 and xcb_xkb.found()
plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed()
alsa = dependency('alsa')
udev = dependency('libudev')
json = dependency('json-c')
mpd = dependency('libmpdclient')
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
# Module name -> (source-list, dep-list)
mod_data = {}
mod_data = {
'alsa': [[], [m, alsa]],
'backlight': [[], [m, udev]],
'battery': [[], [udev]],
'clock': [[], []],
'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
'label': [[], []],
'mpd': [[], [mpd]],
'network': [[], []],
'removables': [[], [dynlist, udev]],
'script': [[], []],
'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
}
if plugin_alsa_enabled
mod_data += {'alsa': [[], [m, alsa]]}
if backend_x11
mod_data += {
'xkb': [[], [xcb_stuff, xcb_xkb]],
'xwindow': [[], [xcb_stuff]],
}
endif
if plugin_backlight_enabled
mod_data += {'backlight': [[], [m, udev_backlight]]}
endif
if plugin_battery_enabled
mod_data += {'battery': [[], [udev_battery]]}
endif
if plugin_clock_enabled
mod_data += {'clock': [[], []]}
endif
if plugin_cpu_enabled
mod_data += {'cpu': [[], [m, dynlist]]}
endif
if plugin_disk_io_enabled
mod_data += {'disk-io': [[], [dynlist]]}
endif
if plugin_dwl_enabled
mod_data += {'dwl': [[], [dynlist]]}
endif
if plugin_mem_enabled
mod_data += {'mem': [[], [m]]}
endif
if plugin_mpd_enabled
mod_data += {'mpd': [[], [mpd]]}
endif
if plugin_i3_enabled
mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]}
endif
if plugin_label_enabled
mod_data += {'label': [[], []]}
endif
if plugin_network_enabled
mod_data += {'network': [[], [dynlist]]}
endif
if plugin_pipewire_enabled
mod_data += {'pipewire': [[], [m, pipewire, dynlist, json_pipewire]]}
endif
if plugin_pulse_enabled
mod_data += {'pulse': [[], [m, pulse]]}
endif
if plugin_removables_enabled
mod_data += {'removables': [[], [dynlist, udev_removables]]}
endif
if plugin_script_enabled
mod_data += {'script': [[], []]}
endif
if plugin_sway_xkb_enabled
mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]}
endif
if plugin_niri_language_enabled
mod_data += {'niri-language': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_language]]}
endif
if plugin_niri_workspaces_enabled
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
endif
if plugin_xkb_enabled
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
endif
if plugin_xwindow_enabled
mod_data += {'xwindow': [[], [xcb_stuff]]}
endif
if plugin_river_enabled
if backend_wayland
river_proto_headers = []
river_proto_src = []
@ -162,10 +49,10 @@ if plugin_river_enabled
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach
mod_data += {'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist, wayland_client]]}
endif
mod_data += {
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]],
}
if plugin_foreign_toplevel_enabled
ftop_proto_headers = []
ftop_proto_src = []
@ -184,7 +71,9 @@ if plugin_foreign_toplevel_enabled
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach
mod_data += {'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [m, dynlist, wayland_client]]}
mod_data += {
'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [dynlist]],
}
endif
foreach mod, data : mod_data

View file

@ -1,34 +1,33 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <time.h>
#include <threads.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
#include <poll.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/eventfd.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <mpd/client.h>
#define LOG_MODULE "mpd"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../plugin.h"
struct private
{
struct private {
char *host;
uint16_t port;
struct particle *label;
@ -39,12 +38,10 @@ struct private
bool repeat;
bool random;
bool consume;
bool single;
int volume;
int volume;
char *album;
char *artist;
char *title;
char *file;
struct {
uint64_t value;
@ -62,9 +59,11 @@ destroy(struct module *mod)
struct private *m = mod->private;
if (m->refresh_thread_id != 0) {
assert(m->refresh_abort_fd != -1);
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t))
!= sizeof(uint64_t))
{
LOG_ERRNO("failed to signal abort to refresher thread");
} else {
} else{
int res;
thrd_join(m->refresh_thread_id, &res);
}
@ -76,7 +75,6 @@ destroy(struct module *mod)
free(m->album);
free(m->artist);
free(m->title);
free(m->file);
assert(m->conn == NULL);
m->label->destroy(m->label);
@ -86,7 +84,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "mpd";
}
@ -132,11 +130,12 @@ content(struct module *mod)
if (m->state == MPD_STATE_PLAY) {
elapsed += timespec_diff_milli_seconds(&now, &m->elapsed.when);
if (elapsed > m->duration) {
LOG_DBG("dynamic update of elapsed overflowed: "
"elapsed=%" PRIu64 ", duration=%" PRIu64,
elapsed, m->duration);
LOG_DBG(
"dynamic update of elapsed overflowed: "
"elapsed=%"PRIu64", duration=%"PRIu64, elapsed, m->duration);
elapsed = m->duration;
}
}
unsigned elapsed_secs = elapsed / 1000;
@ -153,23 +152,16 @@ content(struct module *mod)
state_str = "offline";
else {
switch (m->state) {
case MPD_STATE_UNKNOWN:
state_str = "unknown";
break;
case MPD_STATE_STOP:
state_str = "stopped";
break;
case MPD_STATE_PAUSE:
state_str = "paused";
break;
case MPD_STATE_PLAY:
state_str = "playing";
break;
case MPD_STATE_UNKNOWN: state_str = "unknown"; break;
case MPD_STATE_STOP: state_str = "stopped"; break;
case MPD_STATE_PAUSE: state_str = "paused"; break;
case MPD_STATE_PLAY: state_str = "playing"; break;
}
}
/* Tell particle to real-time track? */
enum tag_realtime_unit realtime = m->state == MPD_STATE_PLAY ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE;
enum tag_realtime_unit realtime = m->state == MPD_STATE_PLAY
? TAG_REALTIME_MSECS : TAG_REALTIME_NONE;
struct tag_set tags = {
.tags = (struct tag *[]){
@ -177,19 +169,17 @@ content(struct module *mod)
tag_new_bool(mod, "repeat", m->repeat),
tag_new_bool(mod, "random", m->random),
tag_new_bool(mod, "consume", m->consume),
tag_new_bool(mod, "single", m->single),
tag_new_int_range(mod, "volume", m->volume, 0, 100),
tag_new_string(mod, "album", m->album),
tag_new_string(mod, "artist", m->artist),
tag_new_string(mod, "title", m->title),
tag_new_string(mod, "file", m->file),
tag_new_string(mod, "pos", pos),
tag_new_string(mod, "end", end),
tag_new_int(mod, "duration", m->duration),
tag_new_int_realtime(
mod, "elapsed", elapsed, 0, m->duration, realtime),
},
.count = 14,
.count = 12,
};
mtx_unlock(&mod->lock);
@ -233,7 +223,7 @@ wait_for_socket_create(const struct module *mod)
struct stat st;
if (stat(m->host, &st) == 0 && S_ISSOCK(st.st_mode)) {
int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1);
@ -244,7 +234,8 @@ wait_for_socket_create(const struct module *mod)
LOG_DBG("%s: already exists, and is connectable", m->host);
have_mpd_socket = true;
} else {
LOG_DBG("%s: already exists, but isn't connectable: %s", m->host, strerror(errno));
LOG_DBG("%s: already exists, but isn't connectable: %s",
m->host, strerror(errno));
}
close(s);
@ -255,15 +246,12 @@ wait_for_socket_create(const struct module *mod)
bool ret = false;
while (!have_mpd_socket) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}};
struct pollfd fds[] = {
{.fd = mod->abort_fd, .events = POLLIN},
{.fd = fd, .events = POLLIN}
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
poll(fds, 2, -1);
if (fds[0].revents & POLLIN) {
ret = true;
@ -275,7 +263,7 @@ wait_for_socket_create(const struct module *mod)
char buf[1024];
ssize_t len = read(fd, buf, sizeof(buf));
for (const char *ptr = buf; ptr < buf + len;) {
for (const char *ptr = buf; ptr < buf + len; ) {
const struct inotify_event *e = (const struct inotify_event *)ptr;
LOG_DBG("inotify: CREATED: %s/%.*s", directory, e->len, e->name);
@ -285,7 +273,7 @@ wait_for_socket_create(const struct module *mod)
break;
}
ptr += sizeof(*e) + e->len;
ptr += sizeof(*e) + e->len;
}
}
@ -308,7 +296,8 @@ connect_to_mpd(const struct module *mod)
enum mpd_error merr = mpd_connection_get_error(conn);
if (merr != MPD_ERROR_SUCCESS) {
LOG_WARN("failed to connect to MPD: %s", mpd_connection_get_error_message(conn));
LOG_WARN("failed to connect to MPD: %s",
mpd_connection_get_error_message(conn));
mpd_connection_free(conn);
return NULL;
}
@ -326,7 +315,8 @@ update_status(struct module *mod)
struct mpd_status *status = mpd_run_status(m->conn);
if (status == NULL) {
LOG_ERR("failed to get status: %s", mpd_connection_get_error_message(m->conn));
LOG_ERR("failed to get status: %s",
mpd_connection_get_error_message(m->conn));
return false;
}
@ -338,7 +328,6 @@ update_status(struct module *mod)
m->repeat = mpd_status_get_repeat(status);
m->random = mpd_status_get_random(status);
m->consume = mpd_status_get_consume(status);
m->single = mpd_status_get_single_state(status) == MPD_SINGLE_ONESHOT;
m->volume = mpd_status_get_volume(status);
m->duration = mpd_status_get_total_time(status) * 1000;
m->elapsed.value = mpd_status_get_elapsed_ms(status);
@ -349,37 +338,30 @@ update_status(struct module *mod)
struct mpd_song *song = mpd_run_current_song(m->conn);
if (song == NULL && mpd_connection_get_error(m->conn) != MPD_ERROR_SUCCESS) {
LOG_ERR("failed to get current song: %s", mpd_connection_get_error_message(m->conn));
LOG_ERR("failed to get current song: %s",
mpd_connection_get_error_message(m->conn));
return false;
}
if (song == NULL) {
mtx_lock(&mod->lock);
free(m->album);
m->album = NULL;
free(m->artist);
m->artist = NULL;
free(m->title);
m->title = NULL;
free(m->file);
m->file = NULL;
free(m->album); m->album = NULL;
free(m->artist); m->artist = NULL;
free(m->title); m->title = NULL;
mtx_unlock(&mod->lock);
} else {
const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
const char *file = mpd_song_get_uri(song);
mtx_lock(&mod->lock);
free(m->album);
free(m->artist);
free(m->title);
free(m->file);
m->album = album != NULL ? strdup(album) : NULL;
m->artist = artist != NULL ? strdup(artist) : NULL;
m->title = title != NULL ? strdup(title) : NULL;
m->file = file != NULL ? strdup(file) : NULL;
mtx_unlock(&mod->lock);
mpd_song_free(song);
@ -395,9 +377,8 @@ run(struct module *mod)
struct private *m = mod->private;
bool aborted = false;
int ret = 0;
while (!aborted && ret == 0) {
while (!aborted) {
if (m->conn != NULL) {
mpd_connection_free(m->conn);
@ -406,21 +387,16 @@ run(struct module *mod)
/* Reset state */
mtx_lock(&mod->lock);
free(m->album);
m->album = NULL;
free(m->artist);
m->artist = NULL;
free(m->title);
m->title = NULL;
free(m->file);
m->file = NULL;
free(m->album); m->album = NULL;
free(m->artist); m->artist = NULL;
free(m->title); m->title = NULL;
m->state = MPD_STATE_UNKNOWN;
m->elapsed.value = m->duration = 0;
m->elapsed.when.tv_sec = m->elapsed.when.tv_nsec = 0;
mtx_unlock(&mod->lock);
/* Keep trying to connect, until we succeed */
while (!aborted && ret == 0) {
while (!aborted) {
if (m->port == 0) {
/* Use inotify to watch for socket creation */
aborted = wait_for_socket_create(mod);
@ -438,33 +414,16 @@ run(struct module *mod)
* host), wait for a while until we try to re-connect
* again.
*/
while (!aborted) {
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 2 * 1000);
if (res < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
ret = 1;
break;
}
if (res == 0) {
ret = 0;
break;
}
else if (res == 1) {
assert(fds[0].revents & POLLIN);
aborted = true;
}
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
int res = poll(fds, 1, 10 * 1000);
if (res == 1) {
assert(fds[0].revents & POLLIN);
aborted = true;
}
}
if (aborted || ret != 0)
if (aborted)
break;
/* Initial state (after establishing a connection) */
@ -482,18 +441,12 @@ run(struct module *mod)
};
if (!mpd_send_idle(m->conn)) {
LOG_ERR("failed to send IDLE command: %s", mpd_connection_get_error_message(m->conn));
LOG_ERR("failed to send IDLE command: %s",
mpd_connection_get_error_message(m->conn));
break;
}
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
ret = 1;
break;
}
poll(fds, 2, -1);
if (fds[0].revents & POLLIN) {
aborted = true;
@ -506,7 +459,8 @@ run(struct module *mod)
}
if (fds[1].revents & POLLIN) {
enum mpd_idle idle __attribute__((unused)) = mpd_recv_idle(m->conn, true);
enum mpd_idle idle __attribute__ ((unused)) =
mpd_recv_idle(m->conn, true);
LOG_DBG("IDLE mask: %d", idle);
@ -523,7 +477,7 @@ run(struct module *mod)
m->conn = NULL;
}
return aborted ? 0 : ret;
return 0;
}
struct refresh_context {
@ -578,7 +532,9 @@ refresh_in(struct module *mod, long milli_seconds)
/* Signal abort to thread */
assert(m->refresh_abort_fd != -1);
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t))
!= sizeof(uint64_t))
{
LOG_ERRNO("failed to signal abort to refresher thread");
return false;
}
@ -618,7 +574,7 @@ refresh_in(struct module *mod, long milli_seconds)
}
/* Detach - we don't want to have to thrd_join() it */
// thrd_detach(tid);
//thrd_detach(tid);
return r == 0;
}
@ -649,8 +605,10 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *port = yml_get_value(node, "port");
const struct yml_node *c = yml_get_value(node, "content");
return mpd_new(yml_value_as_string(host), port != NULL ? yml_value_as_int(port) : 0,
conf_to_particle(c, inherited));
return mpd_new(
yml_value_as_string(host),
port != NULL ? yml_value_as_int(port) : 0,
conf_to_particle(c, inherited));
}
static bool
@ -658,7 +616,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"host", true, &conf_verify_string},
{"port", false, &conf_verify_unsigned},
{"port", false, &conf_verify_int},
MODULE_COMMON_ATTRS,
};

File diff suppressed because it is too large Load diff

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 <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libudev.h>
@ -17,10 +16,10 @@
#define LOG_MODULE "removables"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../bar/bar.h"
#include "../config.h"
#include "../config-verify.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
@ -36,8 +35,8 @@ struct partition {
char *label;
uint64_t size;
bool audio_cd;
/*tll(char *) mount_points;*/
mount_point_list_t mount_points;
};
@ -54,8 +53,7 @@ struct block_device {
tll(struct partition) partitions;
};
struct private
{
struct private {
struct particle *label;
int left_spacing;
int right_spacing;
@ -76,7 +74,8 @@ free_partition(struct partition *p)
static void
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);
free(b->sys_path);
@ -91,7 +90,8 @@ destroy(struct module *mod)
struct private *m = mod->private;
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_and_free(m->ignore, free);
@ -100,7 +100,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "removables";
}
@ -112,23 +112,24 @@ content(struct module *mod)
tll(const struct partition *) partitions = tll_init();
tll_foreach(m->devices, dev)
{
tll_foreach(dev->item.partitions, part) { tll_push_back(partitions, &part->item); }
tll_foreach(m->devices, dev) {
tll_foreach(dev->item.partitions, part) {
tll_push_back(partitions, &part->item);
}
}
struct exposable *exposables[max(tll_length(partitions), 1)];
size_t idx = 0;
tll_foreach(partitions, it)
{
tll_foreach(partitions, it) {
const struct partition *p = it->item;
char dummy_label[16];
const char *label = p->label;
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;
}
@ -140,14 +141,13 @@ content(struct module *mod)
tag_new_string(mod, "vendor", p->block->vendor),
tag_new_string(mod, "model", p->block->model),
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_int_range(mod, "size", p->size, 0, p->block->size),
tag_new_string(mod, "label", label),
tag_new_bool(mod, "mounted", is_mounted),
tag_new_string(mod, "mount_point", mount_point),
},
.count = 9,
.count = 8,
};
exposables[idx++] = m->label->instantiate(m->label, &tags);
@ -155,24 +155,24 @@ content(struct module *mod)
}
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
find_mount_points(const char *dev_path, mount_point_list_t *mount_points)
{
FILE *f = fopen("/proc/self/mountinfo", "re");
if (f == NULL) {
LOG_ERRNO("failed to open /proc/self/mountinfo");
return;
}
FILE *f = fopen("/proc/self/mountinfo", "r");
assert(f != NULL);
char line[4096];
while (fgets(line, sizeof(line), f) != 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);
free(dev);
free(path);
@ -199,11 +199,9 @@ update_mount_points(struct partition *partition)
/* Remove mount points that no longer exists (i.e. old mount
* points that aren't in the new list) */
tll_foreach(partition->mount_points, old)
{
tll_foreach(partition->mount_points, old) {
bool gone = true;
tll_foreach(new_mounts, new)
{
tll_foreach(new_mounts, new) {
if (strcmp(new->item, old->item) == 0) {
/* Remove from new list, as it's already in the
* 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
* 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);
tll_push_back(partition->mount_points, new->item);
@ -237,13 +234,14 @@ update_mount_points(struct partition *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;
const char *_size = udev_device_get_sysattr_value(dev, "size");
uint64_t size = 0;
if (_size != NULL)
sscanf(_size, "%" SCNu64, &size);
sscanf(_size, "%"SCNu64, &size);
#if 0
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");
if (devname != NULL) {
tll_foreach(m->ignore, it)
{
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;
@ -267,70 +264,20 @@ add_partition(struct module *mod, struct block_device *block, struct udev_device
if (label == NULL)
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);
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 = false,
.mount_points = tll_init()}));
struct partition *p = &tll_back(block->partitions);
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()}));
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,
.mount_points = tll_init()}));
struct partition *p = &tll_back(block->partitions);
update_mount_points(p);
@ -340,15 +287,15 @@ add_audio_cd(struct module *mod, struct block_device *block, struct udev_device
}
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);
mtx_lock(&mod->lock);
tll_foreach(block->partitions, it)
{
tll_foreach(block->partitions, it) {
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);
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");
if (devname != NULL) {
tll_foreach(m->ignore, it)
{
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;
@ -389,12 +335,11 @@ add_device(struct module *mod, struct udev_device *dev)
const char *_size = udev_device_get_sysattr_value(dev, "size");
uint64_t size = 0;
if (_size != NULL)
sscanf(_size, "%" SCNu64, &size);
sscanf(_size, "%"SCNu64, &size);
#if 1
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));
}
#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");
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");
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");
unsigned long audio_track_count = _audio_track_count != NULL ? strtoul(_audio_track_count, NULL, 10) : 0;
LOG_DBG("device: add: %s: vendor=%s, model=%s, optical=%d, size=%" PRIu64, 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);
tll_push_back(m->devices, ((struct block_device){.sys_path = strdup(udev_device_get_devpath(dev)),
.dev_path = strdup(udev_device_get_devnode(dev)),
.size = size,
.vendor = vendor != NULL ? strdup(vendor) : NULL,
.model = model != NULL ? strdup(model) : NULL,
.optical = optical,
.media = media,
.partitions = tll_init()}));
tll_push_back(
m->devices,
((struct block_device){
.sys_path = strdup(udev_device_get_devpath(dev)),
.dev_path = strdup(udev_device_get_devnode(dev)),
.size = size,
.vendor = vendor != NULL ? strdup(vendor) : NULL,
.model = model != NULL ? strdup(model) : NULL,
.optical = optical,
.media = media,
.partitions = tll_init()}));
mtx_unlock(&mod->lock);
struct block_device *block = &tll_back(m->devices);
if (optical) {
if (have_fs)
add_partition(mod, block, dev);
else if (audio_track_count > 0)
add_audio_cd(mod, block, dev);
}
if (optical && media)
add_partition(mod, block, dev);
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);
mtx_lock(&mod->lock);
tll_foreach(m->devices, it)
{
tll_foreach(m->devices, it) {
if (strcmp(it->item.sys_path, sys_path) == 0) {
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);
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) {
block = &it->item;
break;
LOG_DBG("device: change: %s", it->item.dev_path);
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);
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);
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)
continue;
@ -560,7 +476,8 @@ handle_udev_event(struct module *mod, struct udev_device *dev)
else if (del)
return del_partition(mod, &it->item, dev);
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;
}
break;
@ -587,15 +504,15 @@ run(struct module *mod)
udev_enumerate_add_match_subsystem(dev_enum, "block");
/* 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_scan_devices(dev_enum);
/* Loop list, and for each device, enumerate its partitions */
struct udev_list_entry *entry = NULL;
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(dev_enum))
{
struct udev_device *dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(entry));
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(dev_enum)) {
struct udev_device *dev = udev_device_new_from_syspath(
udev, udev_list_entry_get_name(entry));
struct block_device *block = add_device(mod, dev);
if (block == NULL) {
@ -612,9 +529,9 @@ run(struct module *mod)
udev_enumerate_scan_devices(part_enum);
struct udev_list_entry *sub_entry = NULL;
udev_list_entry_foreach(sub_entry, udev_enumerate_get_list_entry(part_enum))
{
struct udev_device *partition = udev_device_new_from_syspath(udev, udev_list_entry_get_name(sub_entry));
udev_list_entry_foreach(sub_entry, udev_enumerate_get_list_entry(part_enum)) {
struct udev_device *partition = udev_device_new_from_syspath(
udev, udev_list_entry_get_name(sub_entry));
add_partition(mod, block, partition);
udev_device_unref(partition);
}
@ -628,9 +545,7 @@ run(struct module *mod)
/* To be able to poll() mountinfo for changes, to detect
* mount/unmount operations */
int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC);
int ret = 1;
int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY);
while (true) {
struct pollfd fds[] = {
@ -638,26 +553,16 @@ run(struct module *mod)
{.fd = udev_monitor_get_fd(dev_mon), .events = POLLIN},
{.fd = mount_info_fd, .events = POLLPRI},
};
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
if (errno == EINTR)
continue;
poll(fds, 3, -1);
LOG_ERRNO("failed to poll");
if (fds[0].revents & POLLIN)
break;
}
if (fds[0].revents & POLLIN) {
ret = 0;
break;
}
bool update = false;
if (fds[2].revents & POLLPRI) {
tll_foreach(m->devices, dev)
{
tll_foreach(dev->item.partitions, part)
{
tll_foreach(m->devices, dev) {
tll_foreach(dev->item.partitions, part) {
if (update_mount_points(&part->item))
update = true;
}
@ -666,9 +571,6 @@ run(struct module *mod)
if (fds[1].revents & POLLIN) {
struct udev_device *dev = udev_monitor_receive_device(dev_mon);
if (dev == NULL)
continue;
if (handle_udev_event(mod, dev))
update = true;
udev_device_unref(dev);
@ -682,12 +584,12 @@ run(struct module *mod)
udev_monitor_unref(dev_mon);
udev_unref(udev);
return ret;
return 0;
}
static struct module *
removables_new(struct particle *label, int left_spacing, int right_spacing, size_t ignore_count,
const char *ignore[static ignore_count])
removables_new(struct particle *label, int left_spacing, int right_spacing,
size_t ignore_count, const char *ignore[static ignore_count])
{
struct private *priv = calloc(1, sizeof(*priv));
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 *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 right = spacing != NULL ? yml_value_as_int(spacing)
: right_spacing != NULL ? yml_value_as_int(right_spacing)
: 0;
int left = spacing != NULL ? yml_value_as_int(spacing) :
left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
int right = spacing != NULL ? yml_value_as_int(spacing) :
right_spacing != NULL ? yml_value_as_int(right_spacing) : 0;
size_t ignore_count = ignore_list != NULL ? yml_list_length(ignore_list) : 0;
const char *ignore[max(ignore_count, 1)];
if (ignore_list != NULL) {
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);
}
}
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
@ -743,9 +649,9 @@ static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"spacing", false, &conf_verify_unsigned},
{"left-spacing", false, &conf_verify_unsigned},
{"right-spacing", false, &conf_verify_unsigned},
{"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_int},
{"ignore", false, &verify_ignore},
MODULE_COMMON_ATTRS,
};

View file

@ -1,23 +1,21 @@
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <poll.h>
#include <tllist.h>
#include <wayland-client.h>
#include <tllist.h>
#define LOG_MODULE "river"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
#include "../particles/dynlist.h"
#include "river-status-unstable-v1.h"
#include "xdg-output-unstable-v1.h"
#define min(x, y) ((x) < (y) ? (x) : (y))
struct private;
struct output {
@ -32,9 +30,6 @@ struct output {
uint32_t occupied;
uint32_t focused;
uint32_t urgent;
/* Layout */
char *layout;
};
struct seat {
@ -44,21 +39,19 @@ struct seat {
uint32_t wl_name;
char *name;
char *mode;
char *title;
struct output *output;
};
struct private
{
struct private {
struct module *mod;
bool is_running;
struct zxdg_output_manager_v1 *xdg_output_manager;
struct zriver_status_manager_v1 *status_manager;
struct particle *template;
struct particle *title;
bool all_monitors;
bool is_starting_up;
tll(struct output) outputs;
tll(struct seat) seats;
};
@ -75,7 +68,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "river";
}
@ -89,22 +82,18 @@ content(struct module *mod)
mtx_lock(&m->mod->lock);
if (!m->is_running) {
mtx_unlock(&m->mod->lock);
return dynlist_exposable_new(NULL, 0, 0, 0);
}
uint32_t urgent = 0;
uint32_t occupied = 0;
uint32_t output_focused = 0;
uint32_t seat_focused = 0;
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
const struct output *output = &it->item;
if (!m->all_monitors && output_bar_is_on != NULL && output->name != NULL
&& strcmp(output->name, output_bar_is_on) != 0) {
if (!m->all_monitors &&
output_bar_is_on != NULL && output->name != NULL &&
strcmp(output->name, output_bar_is_on) != 0)
{
continue;
}
@ -112,8 +101,7 @@ content(struct module *mod)
urgent |= output->urgent;
occupied |= output->occupied;
tll_foreach(m->seats, it2)
{
tll_foreach(m->seats, it2) {
const struct seat *seat = &it2->item;
if (seat->output == output) {
seat_focused |= output->focused;
@ -121,7 +109,8 @@ content(struct module *mod)
}
}
const size_t seat_count = m->title != NULL ? tll_length(m->seats) : 0;
const size_t seat_count = m->title != NULL && !m->is_starting_up
? tll_length(m->seats) : 0;
struct exposable *tag_parts[32 + seat_count];
for (unsigned i = 0; i < 32; i++) {
@ -134,11 +123,14 @@ content(struct module *mod)
bool is_urgent = urgent & (1u << i);
bool is_occupied = occupied & (1u << i);
const char *state = is_urgent ? "urgent" : is_visible ? is_focused ? "focused" : "unfocused" : "invisible";
const char *state =
is_urgent ? "urgent" :
is_visible ? is_focused ? "focused" : "unfocused" :
"invisible";
#if 0
LOG_DBG("tag: #%u, visible=%d, focused=%d, occupied=%d, state=%s",
i, is_visible, is_focused, is_occupied & (1u << i), state);
i, visible, focused, occupied & (1u << i), state);
#endif
struct tag_set tags = {
@ -157,21 +149,17 @@ content(struct module *mod)
tag_set_destroy(&tags);
}
if (m->title != NULL) {
if (m->title != NULL && !m->is_starting_up) {
size_t i = 32;
tll_foreach(m->seats, it)
{
tll_foreach(m->seats, it) {
const struct seat *seat = &it->item;
const char *layout = seat->output != NULL && seat->output->layout != NULL ? seat->output->layout : "";
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "seat", seat->name),
tag_new_string(mod, "title", seat->title),
tag_new_string(mod, "mode", seat->mode),
tag_new_string(mod, "layout", layout),
},
.count = 4,
.count = 2,
};
tag_parts[i++] = m->title->instantiate(m->title, &tags);
@ -189,21 +177,15 @@ verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
if (version >= wanted)
return true;
LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version);
LOG_ERR("%s: need interface version %u, but compositor only implements %u",
iface, wanted, version);
return false;
}
static void
output_destroy(struct output *output)
{
tll_foreach(output->m->seats, it)
{
struct seat *seat = &it->item;
if (seat->output == output)
seat->output = NULL;
}
free(output->name);
free(output->layout);
if (output->status != NULL)
zriver_output_status_v1_destroy(output->status);
if (output->xdg_output != NULL)
@ -217,7 +199,6 @@ seat_destroy(struct seat *seat)
{
free(seat->title);
free(seat->name);
free(seat->mode);
if (seat->status != NULL)
zriver_seat_status_v1_destroy(seat->status);
if (seat->wl_seat != NULL)
@ -225,7 +206,8 @@ seat_destroy(struct seat *seat)
}
static void
focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, uint32_t tags)
focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags)
{
struct output *output = data;
@ -242,7 +224,8 @@ focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1
}
static void
view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, struct wl_array *tags)
view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
struct wl_array *tags)
{
struct output *output = data;
struct module *mod = output->m->mod;
@ -254,7 +237,9 @@ view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, s
/* Each entry in the list is a view, and the value is the tags
* associated with that view */
uint32_t *set;
wl_array_for_each(set, tags) { output->occupied |= *set; }
wl_array_for_each(set, tags) {
output->occupied |= *set;
}
LOG_DBG("output: %s: occupied tags: 0x%0x", output->name, output->occupied);
}
@ -263,7 +248,8 @@ view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, s
}
static void
urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, uint32_t tags)
urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags)
{
struct output *output = data;
struct module *mod = output->m->mod;
@ -276,59 +262,23 @@ urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
mod->bar->refresh(mod->bar);
}
#if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION)
static void
layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, const char *name)
{
struct output *output = data;
struct module *mod = output->m->mod;
mtx_lock(&mod->lock);
{
free(output->layout);
output->layout = name != NULL ? strdup(name) : NULL;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
#endif
#if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION)
static void
layout_name_clear(void *data, struct zriver_output_status_v1 *zriver_output_status_v1)
{
struct output *output = data;
struct module *mod = output->m->mod;
mtx_lock(&mod->lock);
{
free(output->layout);
output->layout = NULL;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
#endif
static const struct zriver_output_status_v1_listener river_status_output_listener = {
.focused_tags = &focused_tags,
.view_tags = &view_tags,
.urgent_tags = &urgent_tags,
#if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION)
.layout_name = &layout_name,
#endif
#if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION)
.layout_name_clear = &layout_name_clear,
#endif
};
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
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)
{
}
@ -338,7 +288,8 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
}
static void
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name)
xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output,
const char *name)
{
struct output *output = data;
struct module *mod = output->m->mod;
@ -353,7 +304,8 @@ xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char
}
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)
{
}
@ -366,46 +318,44 @@ static struct zxdg_output_v1_listener xdg_output_listener = {
};
static void
update_output(struct output *output)
instantiate_output(struct output *output)
{
if (output->m->is_starting_up)
return;
assert(output->wl_output != NULL);
if (output->m->status_manager != NULL) {
/*
* Bind river output status, if we have already bound the status manager
*/
if (output->m->status_manager != NULL && output->status == NULL) {
output->status = zriver_status_manager_v1_get_river_output_status(
output->m->status_manager, output->wl_output);
if (output->status != NULL) {
zriver_output_status_v1_destroy(output->status);
output->status = NULL;
}
output->status = zriver_status_manager_v1_get_river_output_status(output->m->status_manager, output->wl_output);
if (output->status != NULL) {
zriver_output_status_v1_add_listener(output->status, &river_status_output_listener, output);
zriver_output_status_v1_add_listener(
output->status, &river_status_output_listener, output);
}
}
if (output->m->xdg_output_manager != NULL && output->xdg_output == NULL) {
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(output->m->xdg_output_manager, output->wl_output);
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
output->m->xdg_output_manager, output->wl_output);
if (output->xdg_output != NULL) {
zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output);
zxdg_output_v1_add_listener(
output->xdg_output, &xdg_output_listener, output);
}
}
}
static void
focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct wl_output *wl_output)
focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *wl_output)
{
struct seat *seat = data;
struct private *m = seat->m;
struct module *mod = m->mod;
struct output *output = NULL;
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
if (it->item.wl_output == wl_output) {
output = &it->item;
break;
@ -426,7 +376,8 @@ focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
}
static void
unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct wl_output *wl_output)
unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
struct wl_output *wl_output)
{
struct seat *seat = data;
struct private *m = seat->m;
@ -435,8 +386,7 @@ unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1
mtx_lock(&mod->lock);
{
struct output *output = NULL;
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
if (it->item.wl_output == wl_output) {
output = &it->item;
break;
@ -454,7 +404,8 @@ unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1
}
static void
focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, const char *title)
focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *title)
{
struct seat *seat = data;
struct module *mod = seat->m->mod;
@ -469,9 +420,11 @@ focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, co
const char *output_bar_is_on = mod->bar->output_name(mod->bar);
if (seat->m->all_monitors
|| (output_bar_is_on != NULL && seat->output != NULL && seat->output->name != NULL
&& strcmp(output_bar_is_on, seat->output->name) == 0)) {
if (seat->m->all_monitors ||
(output_bar_is_on != NULL &&
seat->output != NULL && seat->output->name != NULL &&
strcmp(output_bar_is_on, seat->output->name) == 0))
{
mtx_lock(&mod->lock);
{
free(seat->title);
@ -482,37 +435,15 @@ focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, co
}
}
#if defined(ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION)
static void
mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, const char *name)
{
struct seat *seat = data;
struct module *mod = seat->m->mod;
mtx_lock(&mod->lock);
{
free(seat->mode);
seat->mode = strdup(name);
mtx_unlock(&mod->lock);
}
mod->bar->refresh(mod->bar);
LOG_DBG("seat: %s, current mode: %s", seat->name, seat->mode);
}
#endif
static const struct zriver_seat_status_v1_listener river_seat_status_listener = {
.focused_output = &focused_output,
.unfocused_output = &unfocused_output,
.focused_view = &focused_view,
#if defined(ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION)
.mode = &mode,
#endif
};
static void
seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps)
seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
enum wl_seat_capability caps)
{
}
@ -537,28 +468,29 @@ static const struct wl_seat_listener seat_listener = {
};
static void
update_seat(struct seat *seat)
instantiate_seat(struct seat *seat)
{
assert(seat->wl_seat != NULL);
if (seat->m->is_starting_up)
return;
if (seat->m->status_manager == NULL)
return;
if (seat->status != NULL) {
zriver_seat_status_v1_destroy(seat->status);
seat->status = NULL;
}
seat->status = zriver_status_manager_v1_get_river_seat_status(seat->m->status_manager, seat->wl_seat);
seat->status = zriver_status_manager_v1_get_river_seat_status(
seat->m->status_manager, seat->wl_seat);
if (seat->status == NULL)
return;
zriver_seat_status_v1_add_listener(seat->status, &river_seat_status_listener, seat);
zriver_seat_status_v1_add_listener(
seat->status, &river_seat_status_listener, seat);
}
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 private *m = data;
@ -567,7 +499,8 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
if (!verify_iface_version(interface, version, required))
return;
struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, required);
struct wl_output *wl_output = wl_registry_bind(
registry, name, &wl_output_interface, required);
if (wl_output == NULL)
return;
@ -580,8 +513,7 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
mtx_lock(&m->mod->lock);
tll_push_back(m->outputs, output);
update_output(&tll_back(m->outputs));
tll_foreach(m->seats, it) update_seat(&it->item);
instantiate_output(&tll_back(m->outputs));
mtx_unlock(&m->mod->lock);
}
@ -590,10 +522,12 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
if (!verify_iface_version(interface, version, required))
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(&m->mod->lock);
tll_foreach(m->outputs, it) update_output(&it->item);
tll_foreach(m->outputs, it)
instantiate_output(&it->item);
mtx_unlock(&m->mod->lock);
}
@ -602,7 +536,8 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
if (!verify_iface_version(interface, version, required))
return;
struct wl_seat *wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, required);
struct wl_seat *wl_seat = wl_registry_bind(
registry, name, &wl_seat_interface, required);
if (wl_seat == NULL)
return;
@ -612,7 +547,7 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
struct seat *seat = &tll_back(m->seats);
wl_seat_add_listener(wl_seat, &seat_listener, seat);
update_seat(seat);
instantiate_seat(seat);
mtx_unlock(&m->mod->lock);
}
@ -621,11 +556,14 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, const cha
if (!verify_iface_version(interface, version, required))
return;
m->status_manager = wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, min(version, 4));
m->status_manager = wl_registry_bind(
registry, name, &zriver_status_manager_v1_interface, required);
mtx_lock(&m->mod->lock);
tll_foreach(m->outputs, it) update_output(&it->item);
tll_foreach(m->seats, it) update_seat(&it->item);
tll_foreach(m->outputs, it)
instantiate_output(&it->item);
tll_foreach(m->seats, it)
instantiate_seat(&it->item);
mtx_unlock(&m->mod->lock);
}
}
@ -636,8 +574,7 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
struct private *m = data;
mtx_lock(&m->mod->lock);
tll_foreach(m->outputs, it)
{
tll_foreach(m->outputs, it) {
if (it->item.wl_name == name) {
output_destroy(&it->item);
tll_remove(m->outputs, it);
@ -646,8 +583,7 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
}
}
tll_foreach(m->seats, it)
{
tll_foreach(m->seats, it) {
if (it->item.wl_name == name) {
seat_destroy(&it->item);
tll_remove(m->seats, it);
@ -678,8 +614,9 @@ run(struct module *mod)
goto out;
}
if ((registry = wl_display_get_registry(display)) == NULL
|| wl_registry_add_listener(registry, &registry_listener, m) != 0) {
if ((registry = wl_display_get_registry(display)) == NULL ||
wl_registry_add_listener(registry, &registry_listener, m) != 0)
{
LOG_ERR("failed to get Wayland registry");
goto out;
}
@ -691,10 +628,21 @@ run(struct module *mod)
goto out;
}
m->is_running = true;
wl_display_roundtrip(display);
bool unlock_at_exit = true;
mtx_lock(&mod->lock);
m->is_starting_up = false;
tll_foreach(m->outputs, it)
instantiate_output(&it->item);
tll_foreach(m->seats, it)
instantiate_seat(&it->item);
unlock_at_exit = false;
mtx_unlock(&mod->lock);
while (true) {
wl_display_flush(display);
@ -726,9 +674,11 @@ run(struct module *mod)
ret = 0;
out:
tll_foreach(m->seats, it) seat_destroy(&it->item);
tll_foreach(m->seats, it)
seat_destroy(&it->item);
tll_free(m->seats);
tll_foreach(m->outputs, it) output_destroy(&it->item);
tll_foreach(m->outputs, it)
output_destroy(&it->item);
tll_free(m->outputs);
if (m->xdg_output_manager != NULL)
@ -739,6 +689,9 @@ out:
wl_registry_destroy(registry);
if (display != NULL)
wl_display_disconnect(display);
if (unlock_at_exit)
mtx_unlock(&mod->lock);
return ret;
}
@ -749,6 +702,7 @@ river_new(struct particle *template, struct particle *title, bool all_monitors)
m->template = template;
m->title = title;
m->all_monitors = all_monitors;
m->is_starting_up = true;
struct module *mod = module_common_new();
mod->private = m;
@ -767,8 +721,10 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *title = yml_get_value(node, "title");
const struct yml_node *all_monitors = yml_get_value(node, "all-monitors");
return river_new(conf_to_particle(c, inherited), title != NULL ? conf_to_particle(title, inherited) : NULL,
all_monitors != NULL ? yml_value_as_bool(all_monitors) : false);
return river_new(
conf_to_particle(c, inherited),
title != NULL ? conf_to_particle(title, inherited) : NULL,
all_monitors != NULL ? yml_value_as_bool(all_monitors) : false);
}
static bool

View file

@ -1,31 +1,27 @@
#include <assert.h>
#include <errno.h>
#include <libgen.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/timerfd.h>
#define LOG_MODULE "script"
#define LOG_ENABLE_DBG 0
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../config.h"
#include "../config-verify.h"
#include "../module.h"
#include "../plugin.h"
static const long min_poll_interval = 250;
struct private
{
struct private {
char *path;
size_t argc;
char **argv;
@ -63,10 +59,10 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
static char desc[32];
const struct private *m = mod->private;
struct private *m = mod->private;
char *path = strdup(m->path);
snprintf(desc, sizeof(desc), "script(%s)", basename(path));
@ -111,8 +107,9 @@ process_line(struct module *mod, const char *line, size_t len)
size_t value_len = line + len - _value;
LOG_DBG("%.*s: name=\"%.*s\", type=\"%.*s\", value=\"%.*s\"", (int)len, line, (int)name_len, _name, (int)type_len,
type, (int)value_len, _value);
LOG_DBG("%.*s: name=\"%.*s\", type=\"%.*s\", value=\"%.*s\"",
(int)len, line,
(int)name_len, _name, (int)type_len, type, (int)value_len, _value);
name = malloc(name_len + 1);
memcpy(name, _name, name_len);
@ -165,12 +162,16 @@ process_line(struct module *mod, const char *line, size_t len)
tag = tag_new_float(mod, name, v);
}
else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) || (type_len > 9 && memcmp(type, "realtime:", 9) == 0)) {
else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) ||
(type_len > 9 && memcmp(type, "realtime:", 9) == 0))
{
const char *_start = type + 6;
const char *split = memchr(_start, '-', type_len - 6);
if (split == NULL || split == _start || (split + 1) - type >= type_len) {
LOG_ERR("tag range delimiter ('-') not found in type: %.*s", (int)type_len, type);
LOG_ERR(
"tag range delimiter ('-') not found in type: %.*s",
(int)type_len, type);
goto bad_tag;
}
@ -182,7 +183,9 @@ process_line(struct module *mod, const char *line, size_t len)
long start = 0;
for (size_t i = 0; i < start_len; i++) {
if (!(_start[i] >= '0' && _start[i] <= '9')) {
LOG_ERR("tag range start is not an integer: %.*s", (int)start_len, _start);
LOG_ERR(
"tag range start is not an integer: %.*s",
(int)start_len, _start);
goto bad_tag;
}
@ -193,7 +196,9 @@ process_line(struct module *mod, const char *line, size_t len)
long end = 0;
for (size_t i = 0; i < end_len; i++) {
if (!(_end[i] >= '0' && _end[i] <= '9')) {
LOG_ERR("tag range end is not an integer: %.*s", (int)end_len, _end);
LOG_ERR(
"tag range end is not an integer: %.*s",
(int)end_len, _end);
goto bad_tag;
}
@ -215,7 +220,8 @@ process_line(struct module *mod, const char *line, size_t len)
}
if (v < start || v > end) {
LOG_ERR("tag value is outside range: %ld <= %ld <= %ld", start, v, end);
LOG_ERR("tag value is outside range: %ld <= %ld <= %ld",
start, v, end);
goto bad_tag;
}
@ -289,7 +295,7 @@ data_received(struct module *mod, const char *data, size_t len)
{
struct private *m = mod->private;
while (len > m->recv_buf.sz - m->recv_buf.idx) {
if (len > m->recv_buf.sz - m->recv_buf.idx) {
size_t new_sz = m->recv_buf.sz == 0 ? 1024 : m->recv_buf.sz * 2;
char *new_buf = realloc(m->recv_buf.data, new_sz);
@ -306,21 +312,21 @@ data_received(struct module *mod, const char *data, size_t len)
memcpy(&m->recv_buf.data[m->recv_buf.idx], data, len);
m->recv_buf.idx += len;
while (true) {
const char *eot = memmem(m->recv_buf.data, m->recv_buf.idx, "\n\n", 2);
if (eot == NULL) {
/* End of transaction not yet available */
return true;
}
const size_t transaction_size = eot - m->recv_buf.data + 1;
process_transaction(mod, transaction_size);
assert(m->recv_buf.idx >= transaction_size + 1);
memmove(m->recv_buf.data, &m->recv_buf.data[transaction_size + 1], m->recv_buf.idx - (transaction_size + 1));
m->recv_buf.idx -= transaction_size + 1;
const char *eot = memmem(m->recv_buf.data, m->recv_buf.idx, "\n\n", 2);
if (eot == NULL) {
/* End of transaction not yet available */
return true;
}
const size_t transaction_size = eot - m->recv_buf.data + 1;
process_transaction(mod, transaction_size);
assert(m->recv_buf.idx >= transaction_size + 1);
memmove(m->recv_buf.data,
&m->recv_buf.data[transaction_size + 1],
m->recv_buf.idx - (transaction_size + 1));
m->recv_buf.idx -= transaction_size + 1;
return true;
}
@ -389,7 +395,7 @@ execute_script(struct module *mod)
/* Stdout redirection pipe */
int comm_pipe[2];
if (pipe2(comm_pipe, O_CLOEXEC) < 0) {
if (pipe(comm_pipe) < 0) {
LOG_ERRNO("failed to create stdin/stdout redirection pipe");
close(exec_pipe[0]);
close(exec_pipe[1]);
@ -421,8 +427,11 @@ execute_script(struct module *mod)
sigemptyset(&mask);
const struct sigaction sa = {.sa_handler = SIG_DFL};
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0
|| sigprocmask(SIG_SETMASK, &mask, NULL) < 0) {
if (sigaction(SIGINT, &sa, NULL) < 0 ||
sigaction(SIGTERM, &sa, NULL) < 0 ||
sigaction(SIGCHLD, &sa, NULL) < 0 ||
sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
{
goto fail;
}
@ -434,11 +443,13 @@ execute_script(struct module *mod)
close(comm_pipe[0]);
/* Re-direct stdin/stdout */
int dev_null = open("/dev/null", O_RDONLY | O_CLOEXEC);
int dev_null = open("/dev/null", O_RDONLY);
if (dev_null < 0)
goto fail;
if (dup2(dev_null, STDIN_FILENO) < 0 || dup2(comm_pipe[1], STDOUT_FILENO) < 0) {
if (dup2(dev_null, STDIN_FILENO) < 0 ||
dup2(comm_pipe[1], STDOUT_FILENO) < 0)
{
goto fail;
}
@ -446,6 +457,16 @@ execute_script(struct module *mod)
close(comm_pipe[1]);
comm_pipe[1] = -1;
/* Close *all* other FDs */
for (int i = STDERR_FILENO + 1; i < 65536; i++) {
if (i == exec_pipe[1]) {
/* Needed for error reporting. Automatically closed
* when execvp() succeeds */
continue;
}
close(i);
}
execvp(m->path, argv);
fail:
@ -517,7 +538,9 @@ execute_script(struct module *mod)
usleep(10000);
pid_t waited_pid;
while ((waited_pid = waitpid(pid, NULL, timeout > 0 ? WNOHANG : 0)) == 0) {
while ((waited_pid = waitpid(
pid, NULL, timeout > 0 ? WNOHANG : 0)) == 0)
{
struct timeval now;
gettimeofday(&now, NULL);
@ -529,7 +552,7 @@ execute_script(struct module *mod)
/* Don't spinning */
thrd_yield();
usleep(100000); /* 100ms */
usleep(100000); /* 100ms */
}
if (waited_pid == pid) {
@ -558,19 +581,16 @@ run(struct module *mod)
break;
if (m->aborted)
break;
if (m->poll_interval <= 0)
if (m->poll_interval < 0)
break;
struct timeval now;
if (gettimeofday(&now, NULL) < 0) {
if (gettimeofday(&now, NULL) < 0) {
LOG_ERRNO("failed to get current time");
break;
}
struct timeval poll_interval = {
.tv_sec = m->poll_interval / 1000,
.tv_usec = (m->poll_interval % 1000) * 1000,
};
struct timeval poll_interval = {.tv_sec = m->poll_interval};
struct timeval timeout;
timeradd(&now, &poll_interval, &timeout);
@ -613,10 +633,11 @@ run(struct module *mod)
}
static struct module *
script_new(char *path, size_t argc, const char *const argv[static argc], int poll_interval, struct particle *_content)
script_new(const char *path, size_t argc, const char *const argv[static argc],
int poll_interval, struct particle *_content)
{
struct private *m = calloc(1, sizeof(*m));
m->path = path;
m->path = strdup(path);
m->content = _content;
m->argc = argc;
m->argv = malloc(argc * sizeof(m->argv[0]));
@ -636,7 +657,7 @@ script_new(char *path, size_t argc, const char *const argv[static argc], int pol
static struct module *
from_conf(const struct yml_node *node, struct conf_inherit inherited)
{
const struct yml_node *path_node = yml_get_value(node, "path");
const struct yml_node *path = yml_get_value(node, "path");
const struct yml_node *args = yml_get_value(node, "args");
const struct yml_node *c = yml_get_value(node, "content");
const struct yml_node *poll_interval = yml_get_value(node, "poll-interval");
@ -646,31 +667,18 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
if (args != NULL) {
size_t i = 0;
for (struct yml_list_iter iter = yml_list_iter(args); iter.node != NULL; yml_list_next(&iter), i++) {
for (struct yml_list_iter iter = yml_list_iter(args);
iter.node != NULL;
yml_list_next(&iter), i++)
{
argv[i] = yml_value_as_string(iter.node);
}
}
const char *yml_path = yml_value_as_string(path_node);
char *path = NULL;
if (yml_path[0] == '~' && yml_path[1] == '/') {
const char *home_dir = getenv("HOME");
if (home_dir == NULL) {
LOG_ERRNO("failed to expand '~");
return NULL;
}
if (asprintf(&path, "%s/%s", home_dir, yml_path + 2) < 0) {
LOG_ERRNO("failed to expand '~");
return NULL;
}
} else
path = strdup(yml_path);
return script_new(path, argc, argv, poll_interval != NULL ? yml_value_as_int(poll_interval) : 0,
conf_to_particle(c, inherited));
return script_new(
yml_value_as_string(path), argc, argv,
poll_interval != NULL ? yml_value_as_int(poll_interval) : -1,
conf_to_particle(c, inherited));
}
static bool
@ -680,12 +688,8 @@ conf_verify_path(keychain_t *chain, const struct yml_node *node)
return false;
const char *path = yml_value_as_string(node);
const bool is_tilde = path[0] == '~' && path[1] == '/';
const bool is_absolute = path[0] == '/';
if (!is_tilde && !is_absolute) {
LOG_ERR("%s: path must either be absolute, or begin with '~/'", conf_err_prefix(chain, node));
if (strlen(path) < 1 || path[0] != '/') {
LOG_ERR("%s: path must be absolute", conf_err_prefix(chain, node));
return false;
}
@ -698,27 +702,13 @@ conf_verify_args(keychain_t *chain, const struct yml_node *node)
return conf_verify_list(chain, node, &conf_verify_string);
}
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[] = {
{"path", true, &conf_verify_path},
{"args", false, &conf_verify_args},
{"poll-interval", false, &conf_verify_poll_interval},
{"poll-interval", false, &conf_verify_int},
MODULE_COMMON_ATTRS,
};

View file

@ -3,15 +3,15 @@
#define LOG_MODULE "sway-xkb"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../particles/dynlist.h"
#include "../plugin.h"
#include "i3-common.h"
#include "i3-ipc.h"
#include "i3-common.h"
#define max(x, y) ((x) > (y) ? (x) : (y))
@ -21,8 +21,7 @@ struct input {
char *layout;
};
struct private
{
struct private {
struct particle *template;
int left_spacing;
int right_spacing;
@ -56,7 +55,7 @@ destroy(struct module *mod)
}
static const char *
description(const struct module *mod)
description(struct module *mod)
{
return "sway-xkb";
}
@ -68,7 +67,6 @@ content(struct module *mod)
mtx_lock(&mod->lock);
assert(m->num_existing_inputs <= m->num_inputs);
struct exposable *particles[max(m->num_existing_inputs, 1)];
for (size_t i = 0, j = 0; i < m->num_inputs; i++) {
@ -90,11 +88,12 @@ content(struct module *mod)
}
mtx_unlock(&mod->lock);
return dynlist_exposable_new(particles, m->num_existing_inputs, m->left_spacing, m->right_spacing);
return dynlist_exposable_new(
particles, m->num_existing_inputs, m->left_spacing, m->right_spacing);
}
static bool
handle_input_reply(int sock, int type, const struct json_object *json, void *_mod)
handle_input_reply(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -121,11 +120,9 @@ handle_input_reply(int sock, int type, const struct json_object *json, void *_mo
struct input *input = NULL;
for (size_t i = 0; i < m->num_inputs; i++) {
struct input *maybe_input = &m->inputs[i];
if (strcmp(maybe_input->identifier, id) == 0 && !maybe_input->exists) {
if (strcmp(maybe_input->identifier, id) == 0) {
input = maybe_input;
LOG_DBG("adding: %s", id);
mtx_lock(&mod->lock);
input->exists = true;
m->num_existing_inputs++;
@ -141,7 +138,8 @@ handle_input_reply(int sock, int type, const struct json_object *json, void *_mo
/* Get current/active layout */
struct json_object *layout;
if (!json_object_object_get_ex(obj, "xkb_active_layout_name", &layout))
if (!json_object_object_get_ex(
obj, "xkb_active_layout_name", &layout))
return false;
const char *new_layout_str = json_object_get_string(layout);
@ -160,7 +158,7 @@ handle_input_reply(int sock, int type, const struct json_object *json, void *_mo
}
static bool
handle_input_event(int sock, int type, const struct json_object *json, void *_mod)
handle_input_event(int type, const struct json_object *json, void *_mod)
{
struct module *mod = _mod;
struct private *m = mod->private;
@ -209,36 +207,27 @@ handle_input_event(int sock, int type, const struct json_object *json, void *_mo
return true;
}
if (is_removed) {
if (input->exists) {
LOG_DBG("removing: %s", id);
if (is_removed || is_added) {
mtx_lock(&mod->lock);
assert((is_removed && input->exists) ||
(is_added && !input->exists));
mtx_lock(&mod->lock);
input->exists = false;
m->num_existing_inputs--;
m->dirty = true;
mtx_unlock(&mod->lock);
}
return true;
}
input->exists = !input->exists;
m->num_existing_inputs += is_added ? 1 : -1;
m->dirty = true;
if (is_added) {
if (!input->exists) {
LOG_DBG("adding: %s", id);
mtx_unlock(&mod->lock);
mtx_lock(&mod->lock);
input->exists = true;
m->num_existing_inputs++;
m->dirty = true;
mtx_unlock(&mod->lock);
}
if (is_removed)
return true;
/* “fallthrough”, to query current/active layout */
/* let is_added fall through, to update layout */
}
/* Get current/active layout */
struct json_object *layout;
if (!json_object_object_get_ex(obj, "xkb_active_layout_name", &layout))
if (!json_object_object_get_ex(
obj, "xkb_active_layout_name", &layout))
return false;
const char *new_layout_str = json_object_get_string(layout);
@ -278,7 +267,7 @@ run(struct module *mod)
if (!i3_get_socket_address(&addr))
return 1;
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
LOG_ERRNO("failed to create UNIX socket");
return 1;
@ -306,8 +295,8 @@ run(struct module *mod)
}
static struct module *
sway_xkb_new(struct particle *template, const char *identifiers[], size_t num_identifiers, int left_spacing,
int right_spacing)
sway_xkb_new(struct particle *template, const char *identifiers[],
size_t num_identifiers, int left_spacing, int right_spacing)
{
struct private *m = calloc(1, sizeof(*m));
m->template = template;
@ -340,32 +329,40 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
const struct yml_node *left_spacing = yml_get_value(node, "left-spacing");
const struct yml_node *right_spacing = yml_get_value(node, "right-spacing");
int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
int right = spacing != NULL ? yml_value_as_int(spacing)
: right_spacing != NULL ? yml_value_as_int(right_spacing)
: 0;
int left = spacing != NULL ? yml_value_as_int(spacing) :
left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
int right = spacing != NULL ? yml_value_as_int(spacing) :
right_spacing != NULL ? yml_value_as_int(right_spacing) : 0;
const struct yml_node *ids = yml_get_value(node, "identifiers");
const size_t num_ids = yml_list_length(ids);
const char *identifiers[num_ids];
size_t i = 0;
for (struct yml_list_iter it = yml_list_iter(ids); it.node != NULL; yml_list_next(&it), i++) {
for (struct yml_list_iter it = yml_list_iter(ids);
it.node != NULL;
yml_list_next(&it), i++)
{
identifiers[i] = yml_value_as_string(it.node);
}
return sway_xkb_new(conf_to_particle(c, inherited), identifiers, num_ids, left, right);
return sway_xkb_new(
conf_to_particle(c, inherited), identifiers, num_ids, left, right);
}
static bool
verify_identifiers(keychain_t *chain, const struct yml_node *node)
{
if (!yml_is_list(node)) {
LOG_ERR("%s: identifiers must be a list of strings", conf_err_prefix(chain, node));
LOG_ERR("%s: identifiers must be a list of strings",
conf_err_prefix(chain, node));
return false;
}
for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) {
for (struct yml_list_iter it = yml_list_iter(node);
it.node != NULL;
yml_list_next(&it))
{
if (!conf_verify_string(chain, it.node))
return false;
}
@ -377,9 +374,9 @@ static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"spacing", false, &conf_verify_unsigned},
{"left-spacing", false, &conf_verify_unsigned},
{"right-spacing", false, &conf_verify_unsigned},
{"spacing", false, &conf_verify_int},
{"left-spacing", false, &conf_verify_int},
{"right-spacing", false, &conf_verify_int},
{"identifiers", true, &verify_identifiers},
MODULE_COMMON_ATTRS,
};
@ -393,5 +390,5 @@ const struct module_iface module_sway_xkb_iface = {
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_sway_xkb_iface")));
extern const struct module_iface iface __attribute__((weak, alias("module_sway_xkb_iface"))) ;
#endif

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