mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-26 04:15:42 +02:00
Merge branch 'master' into master
This commit is contained in:
commit
47bc23c8f1
69 changed files with 2623 additions and 526 deletions
|
@ -37,6 +37,10 @@ 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
|
||||
|
|
17
.clang-format
Normal file
17
.clang-format
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
BasedOnStyle: GNU
|
||||
IndentWidth: 4
|
||||
---
|
||||
Language: Cpp
|
||||
PointerAlignment: Right
|
||||
ColumnLimit: 120
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterEnum: false
|
||||
AfterClass: false
|
||||
SplitEmptyFunction: true
|
||||
AfterFunction: true
|
||||
AfterStruct: false
|
||||
|
||||
SpaceBeforeParens: ControlStatements
|
||||
Cpp11BracedListStyle: true
|
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 70
|
||||
|
||||
[{meson.build,PKGBUILD}]
|
||||
indent_size = 2
|
||||
|
||||
[*.scd]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = false
|
|
@ -27,6 +27,9 @@ versions:
|
|||
debug:
|
||||
stage: build
|
||||
script:
|
||||
- cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
- apk add gcovr
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
|
@ -55,6 +58,9 @@ debug:
|
|||
release:
|
||||
stage: build
|
||||
script:
|
||||
- cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
- mkdir -p bld/release
|
||||
- cd bld/release
|
||||
- meson --buildtype=minsize ../../
|
||||
|
@ -64,6 +70,9 @@ release:
|
|||
x11_only:
|
||||
stage: build
|
||||
script:
|
||||
- cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../
|
||||
|
@ -73,6 +82,9 @@ x11_only:
|
|||
wayland_only:
|
||||
stage: build
|
||||
script:
|
||||
- cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../
|
||||
|
@ -82,6 +94,9 @@ wayland_only:
|
|||
plugins_as_shared_modules:
|
||||
stage: build
|
||||
script:
|
||||
- cd subprojects
|
||||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
pipeline:
|
||||
codespell:
|
||||
when: { branch: master }
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk add python3
|
||||
|
@ -9,7 +12,10 @@ pipeline:
|
|||
- codespell README.md CHANGELOG.md *.c *.h doc/*.scd
|
||||
|
||||
subprojects:
|
||||
when: { branch: master }
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- apk add git
|
||||
|
@ -19,7 +25,10 @@ pipeline:
|
|||
- cd ..
|
||||
|
||||
x64:
|
||||
when: { branch: master }
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
group: build
|
||||
image: alpine:latest
|
||||
commands:
|
||||
|
@ -74,7 +83,10 @@ pipeline:
|
|||
- cd ../..
|
||||
|
||||
x86:
|
||||
when: { branch: master }
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
group: build
|
||||
image: i386/alpine:latest
|
||||
commands:
|
||||
|
|
191
CHANGELOG.md
191
CHANGELOG.md
|
@ -1,6 +1,7 @@
|
|||
# Changelog
|
||||
|
||||
* [Unreleased](#unreleased)
|
||||
* [1.8.0](#1-8-0)
|
||||
* [1.7.0](#1-7-0)
|
||||
* [1.6.2](#1-6-2)
|
||||
* [1.6.1](#1-6-1)
|
||||
|
@ -9,53 +10,166 @@
|
|||
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
* ramp: can now have custom min and max values
|
||||
(https://codeberg.org/dnkl/yambar/issues/103).
|
||||
|
||||
* 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]).
|
||||
|
||||
[153]: https://codeberg.org/dnkl/yambar/issues/153
|
||||
[159]: https://codeberg.org/dnkl/yambar/issues/159
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* Made `libmpdclient` an optional dependency
|
||||
* battery: unknown battery states are now mapped to ‘unknown’, instead
|
||||
of ‘discharging’.
|
||||
* Minimum required meson version is now 0.58.
|
||||
* **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:
|
||||
...
|
||||
```
|
||||
|
||||
For a more thorough explanation, see the updated map section in the
|
||||
man page for yambar-particles([#137][137] and [#175][175]).
|
||||
|
||||
[137]: https://codeberg.org/dnkl/yambar/issues/137
|
||||
[175]: https://codeberg.org/dnkl/yambar/issues/172
|
||||
|
||||
|
||||
### Deprecated
|
||||
### Removed
|
||||
### 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
|
||||
(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 (https://codeberg.org/dnkl/yambar/issues/116).
|
||||
* i3: fixed “missing workspace indicator” (_err: modules/i3.c:94:
|
||||
workspace reply/event without 'name' and/or 'output', and/or 'focus'
|
||||
properties_).
|
||||
* Slow/laggy behavior when quickly spawning many `on-click` handlers,
|
||||
e.g. when handling mouse wheel events ([#169][169]).
|
||||
* cpu: don’t error out on systems where SMT has been disabled
|
||||
([#172][172]).
|
||||
* examples/dwl-tags: updated parsing of `output` name ([#178][178]).
|
||||
|
||||
[169]: https://codeberg.org/dnkl/yambar/issues/169
|
||||
[172]: https://codeberg.org/dnkl/yambar/issues/172
|
||||
[178]: https://codeberg.org/dnkl/yambar/issues/178
|
||||
|
||||
|
||||
### Security
|
||||
### Contributors
|
||||
|
||||
* Horus
|
||||
|
||||
|
||||
## 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
|
||||
(https://codeberg.org/dnkl/yambar/issues/72).
|
||||
([#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
|
||||
(https://codeberg.org/dnkl/yambar/issues/77).
|
||||
([#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
|
||||
(https://codeberg.org/dnkl/yambar/issues/84).
|
||||
([#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.
|
||||
|
@ -67,7 +181,6 @@
|
|||
* 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.
|
||||
|
@ -77,7 +190,7 @@
|
|||
* 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
|
||||
(https://codeberg.org/dnkl/yambar/issues/86).
|
||||
([#86](https://codeberg.org/dnkl/yambar/issues/86)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
@ -87,7 +200,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 (https://codeberg.org/dnkl/yambar/issues/61).
|
||||
disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)).
|
||||
|
||||
|
||||
### Contributors
|
||||
|
@ -103,39 +216,39 @@
|
|||
|
||||
* Text shaping support.
|
||||
* Support for middle and right mouse buttons, mouse wheel and trackpad
|
||||
scrolling (https://codeberg.org/dnkl/yambar/issues/39).
|
||||
scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)).
|
||||
* script: polling mode. See the new `poll-interval` option
|
||||
(https://codeberg.org/dnkl/yambar/issues/67).
|
||||
([#67](https://codeberg.org/dnkl/yambar/issues/67)).
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* doc: split up **yambar-modules**(5) into multiple man pages, one for
|
||||
each module (https://codeberg.org/dnkl/yambar/issues/15).
|
||||
each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)).
|
||||
* fcft >= 2.4.0 is now required.
|
||||
* sway-xkb: non-keyboard inputs are now ignored
|
||||
(https://codeberg.org/dnkl/yambar/issues/51).
|
||||
([#51](https://codeberg.org/dnkl/yambar/issues/51)).
|
||||
* battery: don’t terminate (causing last status to “freeze”) when
|
||||
failing to update; retry again later
|
||||
(https://codeberg.org/dnkl/yambar/issues/44).
|
||||
([#44](https://codeberg.org/dnkl/yambar/issues/44)).
|
||||
* battery: differentiate "Not Charging" and "Discharging" in state
|
||||
tag of battery module.
|
||||
(https://codeberg.org/dnkl/yambar/issues/57).
|
||||
([#57](https://codeberg.org/dnkl/yambar/issues/57)).
|
||||
* string: use HORIZONTAL ELLIPSIS instead of three regular periods
|
||||
when truncating a string
|
||||
(https://codeberg.org/dnkl/yambar/issues/73).
|
||||
([#73](https://codeberg.org/dnkl/yambar/issues/73)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Crash when merging non-dictionary anchors in the YAML configuration
|
||||
(https://codeberg.org/dnkl/yambar/issues/32).
|
||||
([#32](https://codeberg.org/dnkl/yambar/issues/32)).
|
||||
* Crash in the `ramp` particle when the tag’s value was out-of-bounds
|
||||
(https://codeberg.org/dnkl/yambar/issues/45).
|
||||
([#45](https://codeberg.org/dnkl/yambar/issues/45)).
|
||||
* Crash when a string particle contained `{}`
|
||||
(https://codeberg.org/dnkl/yambar/issues/48).
|
||||
([#48](https://codeberg.org/dnkl/yambar/issues/48)).
|
||||
* `script` module rejecting range tag end values containing the digit
|
||||
`9` (https://codeberg.org/dnkl/yambar/issues/60).
|
||||
`9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)).
|
||||
|
||||
|
||||
### Contributors
|
||||
|
@ -150,7 +263,7 @@
|
|||
|
||||
* i3: workspaces with numerical names are sorted separately from
|
||||
non-numerically named workspaces
|
||||
(https://codeberg.org/dnkl/yambar/issues/30).
|
||||
([#30](https://codeberg.org/dnkl/yambar/issues/30)).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
@ -158,7 +271,7 @@
|
|||
* mpd: `elapsed` tag not working (regression, introduced in 1.6.0).
|
||||
* Wrong background color for (semi-) transparent backgrounds.
|
||||
* battery: stats sometimes getting stuck at 0, or impossibly large
|
||||
values (https://codeberg.org/dnkl/yambar/issues/25).
|
||||
values ([#25](https://codeberg.org/dnkl/yambar/issues/25)).
|
||||
|
||||
|
||||
## 1.6.0
|
||||
|
@ -167,17 +280,17 @@
|
|||
|
||||
* alsa: `percent` tag. This is an integer tag that represents the
|
||||
current volume as a percentage value
|
||||
(https://codeberg.org/dnkl/yambar/issues/10).
|
||||
([#10](https://codeberg.org/dnkl/yambar/issues/10)).
|
||||
* river: added documentation
|
||||
(https://codeberg.org/dnkl/yambar/issues/9).
|
||||
([#9](https://codeberg.org/dnkl/yambar/issues/9)).
|
||||
* script: new module, adds support for custom user scripts
|
||||
(https://codeberg.org/dnkl/yambar/issues/11).
|
||||
([#11](https://codeberg.org/dnkl/yambar/issues/11)).
|
||||
* mpd: `volume` tag. This is a range tag that represents MPD's current
|
||||
volume in percentage (0-100)
|
||||
* i3: `sort` configuration option, that controls how the workspace
|
||||
list is sorted. Can be set to one of `none`, `ascending` or
|
||||
`descending`. Default is `none`
|
||||
(https://codeberg.org/dnkl/yambar/issues/17).
|
||||
([#17](https://codeberg.org/dnkl/yambar/issues/17)).
|
||||
* i3: `mode` tag: the name of the currently active mode
|
||||
|
||||
|
||||
|
@ -187,12 +300,12 @@
|
|||
error”_.
|
||||
* Memory leak when a YAML parsing error was encountered.
|
||||
* clock: update every second when necessary
|
||||
(https://codeberg.org/dnkl/yambar/issues/12).
|
||||
([#12](https://codeberg.org/dnkl/yambar/issues/12)).
|
||||
* mpd: fix compilation with clang
|
||||
(https://codeberg.org/dnkl/yambar/issues/16).
|
||||
([#16](https://codeberg.org/dnkl/yambar/issues/16)).
|
||||
* Crash when the alpha component in a color value was 0.
|
||||
* XCB: Fallback to non-primary monitor when the primary monitor is
|
||||
disconnected (https://codeberg.org/dnkl/yambar/issues/20)
|
||||
disconnected ([#20](https://codeberg.org/dnkl/yambar/issues/20))
|
||||
|
||||
|
||||
### Contributors
|
||||
|
|
4
PKGBUILD
4
PKGBUILD
|
@ -1,5 +1,5 @@
|
|||
pkgname=yambar
|
||||
pkgver=1.7.0
|
||||
pkgver=1.8.0
|
||||
pkgrel=1
|
||||
pkgdesc="Simplistic and highly configurable status panel for X and Wayland"
|
||||
arch=('x86_64' 'aarch64')
|
||||
|
@ -15,7 +15,7 @@ depends=(
|
|||
'libudev.so'
|
||||
'json-c'
|
||||
'libmpdclient'
|
||||
'fcft>=2.4.0')
|
||||
'fcft>=3.0.0' 'fcft<4.0.0')
|
||||
optdepends=('xcb-util-errors: better X error messages')
|
||||
source=()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pkgname=yambar-wayland
|
||||
pkgver=1.7.0
|
||||
pkgver=1.8.0
|
||||
pkgrel=1
|
||||
pkgdesc="Simplistic and highly configurable status panel for Wayland"
|
||||
arch=('x86_64' 'aarch64')
|
||||
|
@ -16,7 +16,7 @@ depends=(
|
|||
'libudev.so'
|
||||
'json-c'
|
||||
'libmpdclient'
|
||||
'fcft>=2.4.0')
|
||||
'fcft>=3.0.0' 'fcft<4.0.0')
|
||||
source=()
|
||||
|
||||
pkgver() {
|
||||
|
|
|
@ -293,6 +293,7 @@ run(struct bar *_bar)
|
|||
}
|
||||
|
||||
set_cursor(_bar, "left_ptr");
|
||||
expose(_bar);
|
||||
|
||||
/* Start modules */
|
||||
thrd_t thrd_left[max(bar->left.count, 1)];
|
||||
|
@ -323,7 +324,6 @@ run(struct bar *_bar)
|
|||
|
||||
LOG_DBG("all modules started");
|
||||
|
||||
refresh(_bar);
|
||||
bar->backend.iface->loop(_bar, &expose, &on_mouse);
|
||||
|
||||
LOG_DBG("shutting down");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../color.h"
|
||||
#include "../font-shaping.h"
|
||||
#include "../module.h"
|
||||
|
||||
struct bar {
|
||||
|
@ -26,6 +27,7 @@ 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;
|
||||
|
|
|
@ -7,11 +7,11 @@ endif
|
|||
|
||||
if backend_wayland
|
||||
wayland_protocols = dependency('wayland-protocols')
|
||||
wayland_protocols_datadir = wayland_protocols.get_pkgconfig_variable('pkgdatadir')
|
||||
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
|
||||
|
||||
wscanner = dependency('wayland-scanner', native: true)
|
||||
wscanner_prog = find_program(
|
||||
wscanner.get_pkgconfig_variable('wayland_scanner'), native: true)
|
||||
wscanner.get_variable('wayland_scanner'), native: true)
|
||||
|
||||
wl_proto_headers = []
|
||||
wl_proto_src = []
|
||||
|
|
350
bar/wayland.c
350
bar/wayland.c
|
@ -7,6 +7,7 @@
|
|||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -45,6 +46,7 @@ struct monitor {
|
|||
struct wl_output *output;
|
||||
struct zxdg_output_v1 *xdg;
|
||||
char *name;
|
||||
uint32_t wl_name;
|
||||
|
||||
int x;
|
||||
int y;
|
||||
|
@ -95,6 +97,7 @@ struct wayland_backend {
|
|||
|
||||
tll(struct monitor) monitors;
|
||||
const struct monitor *monitor;
|
||||
char *last_mapped_monitor;
|
||||
|
||||
int scale;
|
||||
|
||||
|
@ -112,11 +115,11 @@ struct wayland_backend {
|
|||
tll(struct buffer) buffers; /* List of SHM buffers */
|
||||
struct buffer *next_buffer; /* Bar is rendering to this one */
|
||||
struct buffer *pending_buffer; /* Finished, but not yet rendered */
|
||||
struct wl_callback *frame_callback;
|
||||
|
||||
double aggregated_scroll;
|
||||
bool have_discrete;
|
||||
|
||||
void (*bar_expose)(const struct bar *bar);
|
||||
void (*bar_on_mouse)(struct bar *bar, enum mouse_event event,
|
||||
enum mouse_button btn, int x, int y);
|
||||
};
|
||||
|
@ -492,12 +495,35 @@ output_scale(void *data, struct wl_output *wl_output, int32_t factor)
|
|||
}
|
||||
}
|
||||
|
||||
#if defined(WL_OUTPUT_NAME_SINCE_VERSION)
|
||||
static void
|
||||
output_name(void *data, struct wl_output *wl_output, const char *name)
|
||||
{
|
||||
struct monitor *mon = data;
|
||||
free(mon->name);
|
||||
mon->name = name != NULL ? strdup(name) : NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION)
|
||||
static void
|
||||
output_description(void *data, struct wl_output *wl_output,
|
||||
const char *description)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct wl_output_listener output_listener = {
|
||||
.geometry = &output_geometry,
|
||||
.mode = &output_mode,
|
||||
.done = &output_done,
|
||||
.scale = &output_scale,
|
||||
#if defined(WL_OUTPUT_NAME_SINCE_VERSION)
|
||||
.name = &output_name,
|
||||
#endif
|
||||
#if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION)
|
||||
.description = &output_description,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void
|
||||
|
@ -519,6 +545,9 @@ xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output,
|
|||
mon->height_px = height;
|
||||
}
|
||||
|
||||
static bool create_surface(struct wayland_backend *backend);
|
||||
static void destroy_surface(struct wayland_backend *backend);
|
||||
|
||||
static void
|
||||
xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
|
||||
{
|
||||
|
@ -531,13 +560,40 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
|
|||
struct wayland_backend *backend = mon->backend;
|
||||
struct private *bar = backend->bar->private;
|
||||
|
||||
if (bar->monitor != NULL && mon->name != NULL &&
|
||||
strcmp(bar->monitor, mon->name) == 0)
|
||||
{
|
||||
/* User specified a monitor, and this is one */
|
||||
backend->monitor = mon;
|
||||
const bool is_mapped = backend->monitor != NULL;
|
||||
if (is_mapped) {
|
||||
assert(backend->surface != NULL);
|
||||
assert(backend->last_mapped_monitor == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool output_is_our_configured_monitor = (
|
||||
bar->monitor != NULL &&
|
||||
mon->name != NULL &&
|
||||
strcmp(bar->monitor, mon->name) == 0);
|
||||
|
||||
const bool output_is_last_mapped = (
|
||||
backend->last_mapped_monitor != NULL &&
|
||||
mon->name != NULL &&
|
||||
strcmp(backend->last_mapped_monitor, mon->name) == 0);
|
||||
|
||||
if (output_is_our_configured_monitor)
|
||||
LOG_DBG("%s: using this monitor (user configured)", mon->name);
|
||||
else if (output_is_last_mapped)
|
||||
LOG_DBG("%s: using this monitor (last mapped)", mon->name);
|
||||
|
||||
if (output_is_our_configured_monitor || output_is_last_mapped) {
|
||||
/* User specified a monitor, and this is one */
|
||||
backend->monitor = mon;
|
||||
|
||||
free(backend->last_mapped_monitor);
|
||||
backend->last_mapped_monitor = NULL;
|
||||
|
||||
if (create_surface(backend) && update_size(backend)) {
|
||||
if (backend->pipe_fds[1] >= 0)
|
||||
refresh(backend->bar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -610,6 +666,7 @@ handle_global(void *data, struct wl_registry *registry,
|
|||
|
||||
tll_push_back(backend->monitors, ((struct monitor){
|
||||
.backend = backend,
|
||||
.wl_name = name,
|
||||
.output = output}));
|
||||
|
||||
struct monitor *mon = &tll_back(backend->monitors);
|
||||
|
@ -679,9 +736,23 @@ handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
|||
}
|
||||
}
|
||||
|
||||
LOG_WARN("unknown global removed: 0x%08x", name);
|
||||
tll_foreach(backend->monitors, it) {
|
||||
struct monitor *mon = &it->item;
|
||||
if (mon->wl_name == name) {
|
||||
LOG_INFO("%s disconnected/disabled", mon->name);
|
||||
|
||||
/* TODO: need to handle displays and seats */
|
||||
if (mon == backend->monitor) {
|
||||
assert(backend->last_mapped_monitor == NULL);
|
||||
backend->last_mapped_monitor = strdup(mon->name);
|
||||
backend->monitor = NULL;
|
||||
}
|
||||
|
||||
tll_remove(backend->monitors, it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_WARN("unknown global removed: 0x%08x", name);
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
|
@ -703,22 +774,10 @@ layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface,
|
|||
static void
|
||||
layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface)
|
||||
{
|
||||
LOG_DBG("layer surface closed by compositor");
|
||||
|
||||
struct wayland_backend *backend = data;
|
||||
|
||||
/*
|
||||
* Called e.g. when an output is disabled. We don't get a
|
||||
* corresponding event if/when that same output re-appears. So,
|
||||
* for now, we simply shut down. In the future, we _could_ maybe
|
||||
* destroy the surface, listen for output events and re-create the
|
||||
* surface if the same output re-appears.
|
||||
*/
|
||||
LOG_WARN("compositor requested surface be closed - shutting down");
|
||||
|
||||
if (write(backend->bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
LOG_ERRNO("failed to signal abort to modules");
|
||||
}
|
||||
destroy_surface(backend);
|
||||
}
|
||||
|
||||
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
|
||||
|
@ -726,6 +785,82 @@ static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
|
|||
.closed = &layer_surface_closed,
|
||||
};
|
||||
|
||||
static const struct wl_surface_listener surface_listener;
|
||||
|
||||
static bool
|
||||
create_surface(struct wayland_backend *backend)
|
||||
{
|
||||
assert(tll_length(backend->monitors) > 0);
|
||||
assert(backend->surface == NULL);
|
||||
assert(backend->layer_surface == NULL);
|
||||
|
||||
struct bar *_bar = backend->bar;
|
||||
struct private *bar = _bar->private;
|
||||
|
||||
backend->surface = wl_compositor_create_surface(backend->compositor);
|
||||
if (backend->surface == NULL) {
|
||||
LOG_ERR("failed to create panel surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_surface_add_listener(backend->surface, &surface_listener, backend);
|
||||
|
||||
enum zwlr_layer_shell_v1_layer layer = bar->layer == BAR_LAYER_BOTTOM
|
||||
? ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM
|
||||
: ZWLR_LAYER_SHELL_V1_LAYER_TOP;
|
||||
|
||||
backend->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
|
||||
backend->layer_shell, backend->surface,
|
||||
backend->monitor != NULL ? backend->monitor->output : NULL,
|
||||
layer, "panel");
|
||||
|
||||
if (backend->layer_surface == NULL) {
|
||||
LOG_ERR("failed to create layer shell surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
zwlr_layer_surface_v1_add_listener(
|
||||
backend->layer_surface, &layer_surface_listener, backend);
|
||||
|
||||
/* Aligned to top, maximum width */
|
||||
enum zwlr_layer_surface_v1_anchor top_or_bottom = bar->location == BAR_TOP
|
||||
? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
|
||||
: ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
||||
|
||||
zwlr_layer_surface_v1_set_anchor(
|
||||
backend->layer_surface,
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
|
||||
top_or_bottom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_surface(struct wayland_backend *backend)
|
||||
{
|
||||
if (backend->layer_surface != NULL)
|
||||
zwlr_layer_surface_v1_destroy(backend->layer_surface);
|
||||
if (backend->surface != NULL)
|
||||
wl_surface_destroy(backend->surface);
|
||||
if (backend->frame_callback != NULL)
|
||||
wl_callback_destroy(backend->frame_callback);
|
||||
|
||||
if (backend->pending_buffer != NULL)
|
||||
backend->pending_buffer->busy = false;
|
||||
if (backend->next_buffer != NULL)
|
||||
backend->next_buffer->busy = false;
|
||||
|
||||
backend->layer_surface = NULL;
|
||||
backend->surface = NULL;
|
||||
backend->frame_callback = NULL;
|
||||
backend->pending_buffer = NULL;
|
||||
backend->next_buffer = NULL;
|
||||
|
||||
backend->scale = 0;
|
||||
backend->render_scheduled = false;
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_release(void *data, struct wl_buffer *wl_buffer)
|
||||
{
|
||||
|
@ -892,6 +1027,8 @@ update_size(struct wayland_backend *backend)
|
|||
const struct monitor *mon = backend->monitor;
|
||||
const int scale = mon != NULL ? mon->scale : guess_scale(backend);
|
||||
|
||||
assert(backend->surface != NULL);
|
||||
|
||||
if (backend->scale == scale)
|
||||
return true;
|
||||
|
||||
|
@ -942,8 +1079,6 @@ update_size(struct wayland_backend *backend)
|
|||
return true;
|
||||
}
|
||||
|
||||
static const struct wl_surface_listener surface_listener;
|
||||
|
||||
static bool
|
||||
setup(struct bar *_bar)
|
||||
{
|
||||
|
@ -989,48 +1124,18 @@ setup(struct bar *_bar)
|
|||
/* Trigger listeners registered in previous roundtrip */
|
||||
wl_display_roundtrip(backend->display);
|
||||
|
||||
backend->surface = wl_compositor_create_surface(backend->compositor);
|
||||
if (backend->surface == NULL) {
|
||||
LOG_ERR("failed to create panel surface");
|
||||
return false;
|
||||
if (backend->surface == NULL && backend->layer_surface == NULL) {
|
||||
if (!create_surface(backend))
|
||||
return false;
|
||||
|
||||
if (!update_size(backend))
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_surface_add_listener(backend->surface, &surface_listener, backend);
|
||||
|
||||
enum zwlr_layer_shell_v1_layer layer = bar->layer == BAR_LAYER_BOTTOM
|
||||
? ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM
|
||||
: ZWLR_LAYER_SHELL_V1_LAYER_TOP;
|
||||
|
||||
backend->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
|
||||
backend->layer_shell, backend->surface,
|
||||
backend->monitor != NULL ? backend->monitor->output : NULL,
|
||||
layer, "panel");
|
||||
|
||||
if (backend->layer_surface == NULL) {
|
||||
LOG_ERR("failed to create layer shell surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
zwlr_layer_surface_v1_add_listener(
|
||||
backend->layer_surface, &layer_surface_listener, backend);
|
||||
|
||||
/* Aligned to top, maximum width */
|
||||
enum zwlr_layer_surface_v1_anchor top_or_bottom = bar->location == BAR_TOP
|
||||
? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
|
||||
: ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
||||
|
||||
zwlr_layer_surface_v1_set_anchor(
|
||||
backend->layer_surface,
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
|
||||
top_or_bottom);
|
||||
|
||||
update_size(backend);
|
||||
|
||||
assert(backend->monitor == NULL ||
|
||||
backend->width / backend->monitor->scale <= backend->monitor->width_px);
|
||||
|
||||
if (pipe(backend->pipe_fds) == -1) {
|
||||
if (pipe2(backend->pipe_fds, O_CLOEXEC | O_NONBLOCK) == -1) {
|
||||
LOG_ERRNO("failed to create pipe");
|
||||
return false;
|
||||
}
|
||||
|
@ -1050,16 +1155,6 @@ cleanup(struct bar *_bar)
|
|||
if (backend->pipe_fds[1] >= 0)
|
||||
close(backend->pipe_fds[1]);
|
||||
|
||||
tll_foreach(backend->buffers, it) {
|
||||
if (it->item.wl_buf != NULL)
|
||||
wl_buffer_destroy(it->item.wl_buf);
|
||||
if (it->item.pix != NULL)
|
||||
pixman_image_unref(it->item.pix);
|
||||
|
||||
munmap(it->item.mmapped, it->item.size);
|
||||
tll_remove(backend->buffers, it);
|
||||
}
|
||||
|
||||
tll_foreach(backend->monitors, it) {
|
||||
struct monitor *mon = &it->item;
|
||||
free(mon->name);
|
||||
|
@ -1070,6 +1165,7 @@ cleanup(struct bar *_bar)
|
|||
wl_output_release(mon->output);
|
||||
tll_remove(backend->monitors, it);
|
||||
}
|
||||
free(backend->last_mapped_monitor);
|
||||
|
||||
if (backend->xdg_output_manager != NULL)
|
||||
zxdg_output_manager_v1_destroy(backend->xdg_output_manager);
|
||||
|
@ -1078,12 +1174,20 @@ cleanup(struct bar *_bar)
|
|||
seat_destroy(&it->item);
|
||||
tll_free(backend->seats);
|
||||
|
||||
if (backend->layer_surface != NULL)
|
||||
zwlr_layer_surface_v1_destroy(backend->layer_surface);
|
||||
destroy_surface(backend);
|
||||
|
||||
tll_foreach(backend->buffers, it) {
|
||||
if (it->item.wl_buf != NULL)
|
||||
wl_buffer_destroy(it->item.wl_buf);
|
||||
if (it->item.pix != NULL)
|
||||
pixman_image_unref(it->item.pix);
|
||||
|
||||
munmap(it->item.mmapped, it->item.size);
|
||||
tll_remove(backend->buffers, it);
|
||||
}
|
||||
|
||||
if (backend->layer_shell != NULL)
|
||||
zwlr_layer_shell_v1_destroy(backend->layer_shell);
|
||||
if (backend->surface != NULL)
|
||||
wl_surface_destroy(backend->surface);
|
||||
if (backend->compositor != NULL)
|
||||
wl_compositor_destroy(backend->compositor);
|
||||
if (backend->shm != NULL)
|
||||
|
@ -1108,14 +1212,19 @@ loop(struct bar *_bar,
|
|||
{
|
||||
struct private *bar = _bar->private;
|
||||
struct wayland_backend *backend = bar->backend.data;
|
||||
bool send_abort_to_modules = true;
|
||||
|
||||
pthread_setname_np(pthread_self(), "bar(wayland)");
|
||||
|
||||
backend->bar_expose = expose;
|
||||
backend->bar_on_mouse = on_mouse;
|
||||
|
||||
while (wl_display_prepare_read(backend->display) != 0)
|
||||
wl_display_dispatch_pending(backend->display);
|
||||
while (wl_display_prepare_read(backend->display) != 0) {
|
||||
if (wl_display_dispatch_pending(backend->display) < 0) {
|
||||
LOG_ERRNO("failed to dispatch pending Wayland events");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
wl_display_flush(backend->display);
|
||||
|
||||
while (true) {
|
||||
|
@ -1127,42 +1236,72 @@ loop(struct bar *_bar,
|
|||
|
||||
poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
if (fds[0].revents & POLLIN) {
|
||||
/* Already done by the bar */
|
||||
send_abort_to_modules = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLHUP) {
|
||||
LOG_INFO("disconnected from wayland");
|
||||
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
LOG_ERRNO("failed to signal abort to modules");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[2].revents & POLLIN) {
|
||||
uint8_t command;
|
||||
if (read(backend->pipe_fds[0], &command, sizeof(command))
|
||||
!= sizeof(command))
|
||||
{
|
||||
LOG_ERRNO("failed to read from command pipe");
|
||||
break;
|
||||
bool do_expose = false;
|
||||
|
||||
/* Coalesce “refresh” commands */
|
||||
size_t count = 0;
|
||||
while (true) {
|
||||
uint8_t command;
|
||||
ssize_t r = read(backend->pipe_fds[0], &command, sizeof(command));
|
||||
if (r < 0 && errno == EAGAIN)
|
||||
break;
|
||||
|
||||
if (r != sizeof(command)) {
|
||||
LOG_ERRNO("failed to read from command pipe");
|
||||
goto out;
|
||||
}
|
||||
|
||||
assert(command == 1);
|
||||
if (command == 1) {
|
||||
count++;
|
||||
do_expose = true;
|
||||
}
|
||||
}
|
||||
|
||||
assert(command == 1);
|
||||
expose(_bar);
|
||||
LOG_DBG("coalesced %zu expose commands", count);
|
||||
if (do_expose)
|
||||
expose(_bar);
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
wl_display_read_events(backend->display);
|
||||
if (wl_display_read_events(backend->display) < 0) {
|
||||
LOG_ERRNO("failed to read events from the Wayland socket");
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (wl_display_prepare_read(backend->display) != 0) {
|
||||
if (wl_display_dispatch_pending(backend->display) < 0) {
|
||||
LOG_ERRNO("failed to dispatch pending Wayland events");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
while (wl_display_prepare_read(backend->display) != 0)
|
||||
wl_display_dispatch_pending(backend->display);
|
||||
wl_display_flush(backend->display);
|
||||
}
|
||||
}
|
||||
|
||||
wl_display_cancel_read(backend->display);
|
||||
out:
|
||||
if (!send_abort_to_modules)
|
||||
return;
|
||||
|
||||
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t))
|
||||
!= sizeof(uint64_t))
|
||||
{
|
||||
LOG_ERRNO("failed to signal abort to modules");
|
||||
}
|
||||
|
||||
//wl_display_cancel_read(backend->display);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1195,7 +1334,15 @@ surface_leave(void *data, struct wl_surface *wl_surface,
|
|||
struct wl_output *wl_output)
|
||||
{
|
||||
struct wayland_backend *backend = data;
|
||||
const struct monitor *mon = backend->monitor;
|
||||
|
||||
assert(mon != NULL);
|
||||
assert(mon->output == wl_output);
|
||||
|
||||
backend->monitor = NULL;
|
||||
|
||||
assert(backend->last_mapped_monitor == NULL);
|
||||
backend->last_mapped_monitor = mon->name != NULL ? strdup(mon->name) : NULL;
|
||||
}
|
||||
|
||||
static const struct wl_surface_listener surface_listener = {
|
||||
|
@ -1219,7 +1366,9 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
|
|||
|
||||
backend->render_scheduled = false;
|
||||
|
||||
assert(wl_callback == backend->frame_callback);
|
||||
wl_callback_destroy(wl_callback);
|
||||
backend->frame_callback = NULL;
|
||||
|
||||
if (backend->pending_buffer != NULL) {
|
||||
struct buffer *buffer = backend->pending_buffer;
|
||||
|
@ -1234,6 +1383,7 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
|
|||
wl_surface_commit(backend->surface);
|
||||
wl_display_flush(backend->display);
|
||||
|
||||
backend->frame_callback = cb;
|
||||
backend->pending_buffer = NULL;
|
||||
backend->render_scheduled = true;
|
||||
} else
|
||||
|
@ -1248,6 +1398,9 @@ commit(const struct bar *_bar)
|
|||
|
||||
//printf("commit: %dxl%d\n", backend->width, backend->height);
|
||||
|
||||
if (backend->next_buffer == NULL)
|
||||
return;
|
||||
|
||||
assert(backend->next_buffer != NULL);
|
||||
assert(backend->next_buffer->busy);
|
||||
|
||||
|
@ -1275,6 +1428,7 @@ commit(const struct bar *_bar)
|
|||
wl_display_flush(backend->display);
|
||||
|
||||
backend->render_scheduled = true;
|
||||
backend->frame_callback = cb;
|
||||
}
|
||||
|
||||
backend->next_buffer = get_buffer(backend);
|
||||
|
@ -1322,7 +1476,7 @@ set_cursor(struct bar *_bar, const char *cursor)
|
|||
}
|
||||
|
||||
static const char *
|
||||
output_name(const struct bar *_bar)
|
||||
bar_output_name(const struct bar *_bar)
|
||||
{
|
||||
const struct private *bar = _bar->private;
|
||||
const struct wayland_backend *backend = bar->backend.data;
|
||||
|
@ -1337,5 +1491,5 @@ const struct backend wayland_backend_iface = {
|
|||
.commit = &commit,
|
||||
.refresh = &refresh,
|
||||
.set_cursor = &set_cursor,
|
||||
.output_name = &output_name,
|
||||
.output_name = &bar_output_name,
|
||||
};
|
||||
|
|
|
@ -215,14 +215,14 @@ setup(struct bar *_bar)
|
|||
uint32_t top_pair[2], bottom_pair[2];
|
||||
|
||||
if (bar->location == BAR_TOP) {
|
||||
top_strut = backend->y + bar->height_with_border;
|
||||
top_strut = bar->height_with_border;
|
||||
top_pair[0] = backend->x;
|
||||
top_pair[1] = backend->x + bar->width - 1;
|
||||
|
||||
bottom_strut = 0;
|
||||
bottom_pair[0] = bottom_pair[1] = 0;
|
||||
} else {
|
||||
bottom_strut = screen->height_in_pixels - backend->y;
|
||||
bottom_strut = bar->height_with_border;
|
||||
bottom_pair[0] = backend->x;
|
||||
bottom_pair[1] = backend->x + bar->width - 1;
|
||||
|
||||
|
|
84
char32.c
Normal file
84
char32.c
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include "char32.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#if defined __has_include
|
||||
#if __has_include (<stdc-predef.h>)
|
||||
#include <stdc-predef.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define LOG_MODULE "char32"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* For now, assume we can map directly to the corresponding wchar_t
|
||||
* functions. This is true if:
|
||||
*
|
||||
* - both data types have the same size
|
||||
* - both use the same encoding (though we require that encoding to be UTF-32)
|
||||
*/
|
||||
|
||||
_Static_assert(
|
||||
sizeof(wchar_t) == sizeof(char32_t), "wchar_t vs. char32_t size mismatch");
|
||||
|
||||
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||
#error "char32_t does not use UTF-32"
|
||||
#endif
|
||||
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__)
|
||||
#error "wchar_t does not use UTF-32"
|
||||
#endif
|
||||
|
||||
size_t
|
||||
c32len(const char32_t *s)
|
||||
{
|
||||
return wcslen((const wchar_t *)s);
|
||||
}
|
||||
|
||||
char32_t *
|
||||
ambstoc32(const char *src)
|
||||
{
|
||||
if (src == NULL)
|
||||
return NULL;
|
||||
|
||||
const size_t src_len = strlen(src);
|
||||
|
||||
char32_t *ret = malloc((src_len + 1) * sizeof(ret[0]));
|
||||
if (ret == NULL)
|
||||
return NULL;
|
||||
|
||||
mbstate_t ps = {0};
|
||||
char32_t *out = ret;
|
||||
const char *in = src;
|
||||
const char *const end = src + src_len + 1;
|
||||
|
||||
size_t chars = 0;
|
||||
size_t rc;
|
||||
|
||||
while ((rc = mbrtoc32(out, in, end - in, &ps)) != 0) {
|
||||
switch (rc) {
|
||||
case (size_t)-1:
|
||||
case (size_t)-2:
|
||||
case (size_t)-3:
|
||||
goto err;
|
||||
}
|
||||
|
||||
in += rc;
|
||||
out++;
|
||||
chars++;
|
||||
}
|
||||
|
||||
*out = U'\0';
|
||||
|
||||
ret = realloc(ret, (chars + 1) * sizeof(ret[0]));
|
||||
return ret;
|
||||
|
||||
err:
|
||||
free(ret);
|
||||
return NULL;
|
||||
}
|
7
char32.h
Normal file
7
char32.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <uchar.h>
|
||||
#include <stddef.h>
|
||||
|
||||
size_t c32len(const char32_t *s);
|
||||
char32_t *ambstoc32(const char *src);
|
|
@ -50,6 +50,17 @@ conf_verify_int(keychain_t *chain, const struct yml_node *node)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_unsigned(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (yml_value_is_int(node) && yml_value_as_int(node) >= 0)
|
||||
return true;
|
||||
|
||||
LOG_ERR("%s: value is not a positive integer: '%s'",
|
||||
conf_err_prefix(chain, node), yml_value_as_string(node));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
conf_verify_bool(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
|
@ -217,6 +228,13 @@ conf_verify_font(keychain_t *chain, const struct yml_node *node)
|
|||
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)
|
||||
{
|
||||
|
@ -381,17 +399,17 @@ static bool
|
|||
verify_bar_border(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"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},
|
||||
{"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_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},
|
||||
{"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},
|
||||
};
|
||||
|
||||
|
@ -422,30 +440,31 @@ conf_verify_bar(const struct yml_node *bar)
|
|||
chain_push(&chain, "bar");
|
||||
|
||||
static const struct attr_info attrs[] = {
|
||||
{"height", true, &conf_verify_int},
|
||||
{"height", true, &conf_verify_unsigned},
|
||||
{"location", true, &verify_bar_location},
|
||||
{"background", true, &conf_verify_color},
|
||||
|
||||
{"monitor", false, &conf_verify_string},
|
||||
{"layer", false, &verify_bar_layer},
|
||||
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
|
||||
{"margin", false, &conf_verify_int},
|
||||
{"left-margin", false, &conf_verify_int},
|
||||
{"right-margin", false, &conf_verify_int},
|
||||
{"margin", false, &conf_verify_unsigned},
|
||||
{"left-margin", false, &conf_verify_unsigned},
|
||||
{"right-margin", false, &conf_verify_unsigned},
|
||||
|
||||
{"border", false, &verify_bar_border},
|
||||
{"font", false, &conf_verify_font},
|
||||
{"font-shaping", false, &conf_verify_font_shaping},
|
||||
{"foreground", false, &conf_verify_color},
|
||||
|
||||
{"left", false, &verify_module_list},
|
||||
{"center", false, &verify_module_list},
|
||||
{"right", false, &verify_module_list},
|
||||
|
||||
{"trackpad-sensitivity", false, &conf_verify_int},
|
||||
{"trackpad-sensitivity", false, &conf_verify_unsigned},
|
||||
|
||||
{NULL, false, NULL},
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@ const char *conf_err_prefix(
|
|||
|
||||
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,
|
||||
|
@ -44,6 +45,7 @@ bool conf_verify_dict(keychain_t *chain, const struct yml_node *node,
|
|||
bool conf_verify_on_click(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_color(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_font(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node);
|
||||
|
||||
bool conf_verify_particle(keychain_t *chain, const struct yml_node *node);
|
||||
bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node);
|
||||
|
|
90
config.c
90
config.c
|
@ -4,6 +4,7 @@
|
|||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
|
@ -66,7 +67,76 @@ conf_to_color(const struct yml_node *node)
|
|||
struct fcft_font *
|
||||
conf_to_font(const struct yml_node *node)
|
||||
{
|
||||
return fcft_from_name(1, &(const char *){yml_value_as_string(node)}, NULL);
|
||||
const char *font_spec = yml_value_as_string(node);
|
||||
|
||||
size_t count = 0;
|
||||
size_t size = 0;
|
||||
const char **fonts = NULL;
|
||||
|
||||
char *copy = strdup(font_spec);
|
||||
for (const char *font = strtok(copy, ",");
|
||||
font != NULL;
|
||||
font = strtok(NULL, ","))
|
||||
{
|
||||
/* Trim spaces, strictly speaking not necessary, but looks nice :) */
|
||||
while (isspace(font[0]))
|
||||
font++;
|
||||
|
||||
if (font[0] == '\0')
|
||||
continue;
|
||||
|
||||
if (count + 1 > size) {
|
||||
size += 4;
|
||||
fonts = realloc(fonts, size * sizeof(fonts[0]));
|
||||
}
|
||||
|
||||
assert(count + 1 <= size);
|
||||
fonts[count++] = font;
|
||||
}
|
||||
|
||||
struct fcft_font *ret = fcft_from_name(count, fonts, NULL);
|
||||
|
||||
free(fonts);
|
||||
free(copy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum font_shaping
|
||||
conf_to_font_shaping(const struct yml_node *node)
|
||||
{
|
||||
const char *v = yml_value_as_string(node);
|
||||
|
||||
if (strcmp(v, "none") == 0)
|
||||
return FONT_SHAPE_NONE;
|
||||
|
||||
else if (strcmp(v, "graphemes") == 0) {
|
||||
static bool have_warned = false;
|
||||
|
||||
if (!have_warned &&
|
||||
!(fcft_capabilities() & FCFT_CAPABILITY_GRAPHEME_SHAPING))
|
||||
{
|
||||
have_warned = true;
|
||||
LOG_WARN("cannot enable grapheme shaping; no support in fcft");
|
||||
}
|
||||
return FONT_SHAPE_GRAPHEMES;
|
||||
}
|
||||
|
||||
else if (strcmp(v, "full") == 0) {
|
||||
static bool have_warned = false;
|
||||
|
||||
if (!have_warned &&
|
||||
!(fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING))
|
||||
{
|
||||
have_warned = true;
|
||||
LOG_WARN("cannot enable full text shaping; no support in fcft");
|
||||
}
|
||||
return FONT_SHAPE_FULL;
|
||||
}
|
||||
|
||||
else {
|
||||
assert(false);
|
||||
return FONT_SHAPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
struct deco *
|
||||
|
@ -112,7 +182,8 @@ particle_simple_list_from_config(const struct yml_node *node,
|
|||
}
|
||||
|
||||
struct particle *common = particle_common_new(
|
||||
0, 0, NULL, fcft_clone(inherited.font), inherited.foreground, NULL);
|
||||
0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping,
|
||||
inherited.foreground, NULL);
|
||||
|
||||
return particle_list_new(common, parts, count, 0, 2);
|
||||
}
|
||||
|
@ -131,6 +202,7 @@ 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");
|
||||
|
||||
|
@ -183,12 +255,14 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited)
|
|||
*/
|
||||
struct fcft_font *font = font_node != NULL
|
||||
? conf_to_font(font_node) : fcft_clone(inherited.font);
|
||||
enum font_shaping font_shaping = font_shaping_node != NULL
|
||||
? conf_to_font_shaping(font_shaping_node) : inherited.font_shaping;
|
||||
pixman_color_t foreground = foreground_node != NULL
|
||||
? conf_to_color(foreground_node) : inherited.foreground;
|
||||
|
||||
/* Instantiate base/common particle */
|
||||
struct particle *common = particle_common_new(
|
||||
left, right, on_click_templates, font, foreground, deco);
|
||||
left, right, on_click_templates, font, font_shaping, foreground, deco);
|
||||
|
||||
const struct particle_iface *iface = plugin_load_particle(type);
|
||||
|
||||
|
@ -205,6 +279,7 @@ 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,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -327,6 +402,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
* foreground color at top-level.
|
||||
*/
|
||||
struct fcft_font *font = fcft_from_name(1, &(const char *){"sans"}, NULL);
|
||||
enum font_shaping font_shaping = FONT_SHAPE_FULL;
|
||||
pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */
|
||||
|
||||
const struct yml_node *font_node = yml_get_value(bar, "font");
|
||||
|
@ -335,12 +411,17 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
|
|||
font = conf_to_font(font_node);
|
||||
}
|
||||
|
||||
const struct yml_node *font_shaping_node = yml_get_value(bar, "font-shaping");
|
||||
if (font_shaping_node != NULL)
|
||||
font_shaping = conf_to_font_shaping(font_shaping_node);
|
||||
|
||||
const struct yml_node *foreground_node = yml_get_value(bar, "foreground");
|
||||
if (foreground_node != NULL)
|
||||
foreground = conf_to_color(foreground_node);
|
||||
|
||||
struct conf_inherit inherited = {
|
||||
.font = font,
|
||||
.font_shaping = font_shaping,
|
||||
.foreground = foreground,
|
||||
};
|
||||
|
||||
|
@ -370,12 +451,15 @@ 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");
|
||||
|
||||
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,
|
||||
};
|
||||
|
|
3
config.h
3
config.h
|
@ -3,6 +3,7 @@
|
|||
#include <fcft/fcft.h>
|
||||
#include "yml.h"
|
||||
#include "bar/bar.h"
|
||||
#include "font-shaping.h"
|
||||
|
||||
struct bar;
|
||||
struct particle;
|
||||
|
@ -16,9 +17,11 @@ 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;
|
||||
};
|
||||
|
||||
|
|
93
decorations/border.c
Normal file
93
decorations/border.c
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../decoration.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
#define LOG_MODULE "border"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
struct private {
|
||||
pixman_color_t color;
|
||||
int size;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct deco *deco)
|
||||
{
|
||||
struct private *d = deco->private;
|
||||
free(d);
|
||||
free(deco);
|
||||
}
|
||||
|
||||
static void
|
||||
expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height)
|
||||
{
|
||||
const struct private *d = deco->private;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix, &d->color, 4,
|
||||
(pixman_rectangle16_t []){
|
||||
/* Top */
|
||||
{x, y, width, min(d->size, height)},
|
||||
|
||||
/* Bottom */
|
||||
{x, max(y + height - d->size, y), width, min(d->size, height)},
|
||||
|
||||
/* Left */
|
||||
{x, y, min(d->size, width), height},
|
||||
|
||||
/* Right */
|
||||
{max(x + width - d->size, x), y, min(d->size, width), height},
|
||||
});
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
border_new(pixman_color_t color, int size)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->color = color;
|
||||
priv->size = size;
|
||||
|
||||
struct deco *deco = calloc(1, sizeof(*deco));
|
||||
deco->private = priv;
|
||||
deco->expose = &expose;
|
||||
deco->destroy = &destroy;
|
||||
|
||||
return deco;
|
||||
}
|
||||
|
||||
static struct deco *
|
||||
from_conf(const struct yml_node *node)
|
||||
{
|
||||
const struct yml_node *color = yml_get_value(node, "color");
|
||||
const struct yml_node *size = yml_get_value(node, "size");
|
||||
return border_new(
|
||||
conf_to_color(color),
|
||||
size != NULL ? yml_value_as_int(size) : 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"color", true, &conf_verify_color},
|
||||
{"size", false, &conf_verify_unsigned},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct deco_iface deco_border_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct deco_iface iface __attribute__((weak, alias("deco_border_iface")));
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
|
||||
|
||||
decorations = []
|
||||
foreach deco : ['background', 'stack', 'underline']
|
||||
foreach deco : ['background', 'border', 'stack', 'underline', 'overline']
|
||||
if plugs_as_libs
|
||||
shared_module('@0@'.format(deco), '@0@.c'.format(deco),
|
||||
dependencies: deco_sdk,
|
||||
|
|
72
decorations/overline.c
Normal file
72
decorations/overline.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "../config-verify.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
|
|
@ -54,7 +54,7 @@ static bool
|
|||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"size", true, &conf_verify_int},
|
||||
{"size", true, &conf_verify_unsigned},
|
||||
{"color", true, &conf_verify_color},
|
||||
DECORATION_COMMON_ATTRS,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
sh = find_program('sh', native: true)
|
||||
|
||||
scdoc = dependency('scdoc', native: true)
|
||||
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
|
||||
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
|
||||
|
||||
foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
|
||||
'yambar-modules-alsa.5.scd', 'yambar-modules-backlight.5.scd',
|
||||
|
@ -13,6 +13,8 @@ foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.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-modules-cpu.5.scd',
|
||||
'yambar-modules-mem.5.scd',
|
||||
'yambar-particles.5.scd', 'yambar-tags.5.scd']
|
||||
parts = man_src.split('.')
|
||||
name = parts[-3]
|
||||
|
@ -23,7 +25,7 @@ foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd',
|
|||
out,
|
||||
output: out,
|
||||
input: man_src,
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
|
||||
capture: true,
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
|
||||
|
|
|
@ -70,6 +70,71 @@ content:
|
|||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# OVERLINE
|
||||
|
||||
Similar to _underline_, this decoration renders a line of configurable
|
||||
size and color at the top of the particle.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| size
|
||||
: int
|
||||
: yes
|
||||
: The size (height/thickness) of the line, in pixels
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
: The color of the line. See *yambar*(5) for format.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
string:
|
||||
deco:
|
||||
overline:
|
||||
size: 2
|
||||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# BORDER
|
||||
|
||||
This decoration renders a border of configurable size (i.e border
|
||||
width) around the particle.
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| color
|
||||
: color
|
||||
: yes
|
||||
: The color of the line. See *yambar*(5) for format.
|
||||
| size
|
||||
: int
|
||||
: no
|
||||
: Border width, in pixels. Defaults to 1px.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
string:
|
||||
deco:
|
||||
border:
|
||||
size: 2
|
||||
color: ff0000ff
|
||||
```
|
||||
|
||||
|
||||
# STACK
|
||||
|
||||
This particles combines multiple decorations.
|
||||
|
|
|
@ -8,6 +8,17 @@ 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*
|
||||
|
|
42
doc/yambar-modules-cpu.5.scd
Normal file
42
doc/yambar-modules-cpu.5.scd
Normal file
|
@ -0,0 +1,42 @@
|
|||
yambar-modules-cpu(5)
|
||||
|
||||
# NAME
|
||||
cpu - This module provides the CPU usage
|
||||
|
||||
# TAGS
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Description*
|
||||
| cpu
|
||||
: range
|
||||
: Current usage of the whole CPU in percent
|
||||
| cpu<0..X>
|
||||
: range
|
||||
: Current usage of CPU core X in percent
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| interval
|
||||
: int
|
||||
: no
|
||||
: Refresh interval of the CPU usage stats in ms (default=500). Cannot be less then 500 ms
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- cpu:
|
||||
interval: 2500
|
||||
content:
|
||||
string: {text: "{cpu1}%"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
|
@ -67,10 +67,9 @@ bar:
|
|||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
tag: activated
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: "{app-id}: {title}"}
|
||||
```
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
| urgent
|
||||
: bool
|
||||
: True if the workspace has the urgent flag set
|
||||
| empty
|
||||
: bool
|
||||
: True if the workspace is empty (Sway only)
|
||||
| state
|
||||
: string
|
||||
: One of *urgent*, *focused*, *unfocused* or *invisible* (note:
|
||||
|
@ -65,6 +68,10 @@ with the _application_ and _title_ tags to replace the X11-only
|
|||
: enum
|
||||
: no
|
||||
: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_.
|
||||
| strip-workspace-numbers
|
||||
: bool
|
||||
: no
|
||||
: If true, *N:* prefixes will be stripped from workspace names. Useful together with *sort*, to have the workspace order fixed.
|
||||
| persistent
|
||||
: list of strings
|
||||
: no
|
||||
|
@ -95,10 +102,9 @@ bar:
|
|||
content:
|
||||
"":
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: "{name}"}}
|
||||
values:
|
||||
focused: {string: {text: "{name}*"}}
|
||||
conditions:
|
||||
state == focused: {string: {text: "{name}*"}}
|
||||
current: { string: {text: "{application}: {title}"}}
|
||||
```
|
||||
|
||||
|
|
51
doc/yambar-modules-mem.5.scd
Normal file
51
doc/yambar-modules-mem.5.scd
Normal file
|
@ -0,0 +1,51 @@
|
|||
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*
|
||||
| interval
|
||||
: string
|
||||
: no
|
||||
: Refresh interval of the memory usage stats in ms (default=500). Cannot be less then 500 ms
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
bar:
|
||||
left:
|
||||
- mem:
|
||||
interval: 2500
|
||||
content:
|
||||
string: {text: "{used:mb}MB"}
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
|
@ -22,6 +22,10 @@ instantiates the provided _content_ particle for each detected drive.
|
|||
| optical
|
||||
: bool
|
||||
: True if the drive is an optical drive (CD-ROM, DVD-ROM etc)
|
||||
| audio
|
||||
: bool
|
||||
: True if an optical drive has an audio CD inserted (i.e. this
|
||||
property is always false for non-optical drives).
|
||||
| device
|
||||
: string
|
||||
: Volume device name (typically */dev/sd?*)
|
||||
|
@ -70,13 +74,12 @@ bar:
|
|||
- removables:
|
||||
content:
|
||||
map:
|
||||
tag: mounted
|
||||
values:
|
||||
false:
|
||||
conditions:
|
||||
~mounted:
|
||||
string:
|
||||
on-click: udisksctl mount -b {device}
|
||||
text: "{label}"
|
||||
true:
|
||||
mounted:
|
||||
string:
|
||||
on-click: udisksctl unmount -b {device}
|
||||
text: "{label}"
|
||||
|
|
|
@ -79,10 +79,9 @@ bar:
|
|||
title: {string: { text: "{seat} - {title}" }}
|
||||
content:
|
||||
map:
|
||||
tag: occupied
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~occupied: {empty: {}}
|
||||
occupied:
|
||||
string:
|
||||
margin: 5
|
||||
text: "{id}: {state}"
|
||||
|
|
|
@ -77,6 +77,7 @@ User defined.
|
|||
: Arguments to pass to the script/binary.
|
||||
| poll-interval
|
||||
: integer
|
||||
: no
|
||||
: Number of seconds between each script run. If unset, continuous mode
|
||||
is used.
|
||||
|
||||
|
@ -132,10 +133,9 @@ bar:
|
|||
title|string|{{title}}
|
||||
content:
|
||||
map:
|
||||
tag: status
|
||||
values:
|
||||
Paused: {empty: {}}
|
||||
Playing:
|
||||
conditions:
|
||||
status == Paused: {empty: {}}
|
||||
status == Playing:
|
||||
content: {string: {text: "{artist} - {title}"}}
|
||||
```
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ instantiated from this template, and represents an input device.
|
|||
| identifiers
|
||||
: list of strings
|
||||
: yes
|
||||
: Identifiers of input devices to monitor. Use _swaymsg -t get_inputs | grep 'identifier.*keyboard' | cut -d'"' -f4_ to see available devices.
|
||||
: Identifiers of input devices to monitor. Use _swaymsg -t get_inputs | grep 'identifier.\*keyboard' | cut -d'"' -f4_ to see available devices.
|
||||
| content
|
||||
: particle
|
||||
: yes
|
||||
|
|
|
@ -68,20 +68,17 @@ in red.
|
|||
```
|
||||
content:
|
||||
map:
|
||||
tag: carrier
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~carrier: {empty: {}}
|
||||
carrier:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
up:
|
||||
conditions:
|
||||
state == up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
```
|
||||
|
||||
## Use yaml anchors
|
||||
|
|
|
@ -31,20 +31,67 @@ 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. The same applies to all attributes associated with
|
||||
it, below.
|
||||
| on-click.left
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is clicked. Tags can be
|
||||
used. Note that the string is *not* executed in a shell.
|
||||
: Command to execute when the particle is left-clicked.
|
||||
| on-click.right
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is right-clicked.
|
||||
| on-click.middle
|
||||
: string
|
||||
: no
|
||||
: Command to execute when the particle is middle-clicked.
|
||||
| on-click.wheel-up
|
||||
: string
|
||||
: no
|
||||
: Command to execute every time a 'wheel-up' event is triggered.
|
||||
| on-click.wheel-down
|
||||
: string
|
||||
: no
|
||||
: Command to execute every time a 'wheel-down' event is triggered.
|
||||
| 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
|
||||
|
@ -161,48 +208,102 @@ content:
|
|||
# MAP
|
||||
|
||||
This particle maps the values of a specific tag to different
|
||||
particles. In addition to explicit tag values, you can also specify a
|
||||
particles based on conditions. A condition takes either the form of:
|
||||
|
||||
<tag> <operation> <value>
|
||||
|
||||
Or, for boolean tags:
|
||||
|
||||
<tag>
|
||||
|
||||
Where <tag> is the tag you would like to map, <operation> is one of:
|
||||
|
||||
[- ==
|
||||
:- !=
|
||||
:- >=
|
||||
:- >
|
||||
:- <=
|
||||
:- <
|
||||
|
||||
and <value> is the value you would like to compare it to.
|
||||
|
||||
For boolean tags, negation is done with a preceding '~':
|
||||
|
||||
~<tag>
|
||||
|
||||
To match for empty strings, use ' "" ':
|
||||
|
||||
<tag> == ""
|
||||
|
||||
In addition to explicit tag values, you can also specify a
|
||||
default/fallback particle.
|
||||
|
||||
Note that conditions are evaluated in the order they appear. *If
|
||||
multiple conditions are true, the first one will be used*. This means
|
||||
that in a configuration such as:
|
||||
|
||||
```
|
||||
tx-bitrate > 1000:
|
||||
tx-bitrate > 1000000:
|
||||
```
|
||||
|
||||
the second condition would never run, since whenever the second
|
||||
condition is true, the first is also true. The correct way of doing
|
||||
this would be to invert the order of the conditions:
|
||||
|
||||
```
|
||||
tx-bitrate > 1000000:
|
||||
tx-bitrate > 1000:
|
||||
```
|
||||
|
||||
|
||||
## CONFIGURATION
|
||||
|
||||
[[ *Name*
|
||||
:[ *Type*
|
||||
:[ *Req*
|
||||
:[ *Description*
|
||||
| tag
|
||||
: string
|
||||
: yes
|
||||
: The tag (name of) which values should be mapped
|
||||
| values
|
||||
| conditions
|
||||
: associative array
|
||||
: yes
|
||||
: An associative array of tag values mapped to particles
|
||||
: An associative array of conditions (see above) mapped to particles
|
||||
| default
|
||||
: particle
|
||||
: no
|
||||
: Default particle to use, when tag's value does not match any of the
|
||||
mapped values.
|
||||
: Default particle to use, none of the conditions are true
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
content:
|
||||
map:
|
||||
tag: tag_name
|
||||
default:
|
||||
string:
|
||||
text: this is the default particle; the tag's value is now {tag_name}
|
||||
values:
|
||||
one_value:
|
||||
conditions:
|
||||
tag == one_value:
|
||||
string:
|
||||
text: tag's value is now one_value
|
||||
another_value:
|
||||
tag == another_value:
|
||||
string:
|
||||
text: tag's value is now another_value
|
||||
|
||||
```
|
||||
|
||||
For a boolean tag:
|
||||
|
||||
```
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
tag:
|
||||
string:
|
||||
text: tag is true
|
||||
~tag:
|
||||
string:
|
||||
text: tag is false
|
||||
```
|
||||
|
||||
# RAMP
|
||||
|
||||
This particle uses a range tag to index into an array of
|
||||
|
|
|
@ -12,7 +12,8 @@ and reference them using anchors.
|
|||
Besides the normal yaml types, there are a couple of yambar specific
|
||||
types that are frequently used:
|
||||
|
||||
- *font*: this is a string in _fontconfig_ format. Example of valid values:
|
||||
- *font*: this is a comma separated list of fonts in _fontconfig_
|
||||
format. Example of valid values:
|
||||
- Font Awesome 5 Brands
|
||||
- Font Awesome 5 Free:style=solid
|
||||
- Dina:pixelsize=10:slant=italic
|
||||
|
@ -124,7 +125,17 @@ types that are frequently used:
|
|||
| font
|
||||
: font
|
||||
: no
|
||||
: Default font to use in modules and particles
|
||||
: Default font to use in modules and particles. May also be a comma
|
||||
separated list of several fonts, in which case the first font is
|
||||
the primary font, and the rest fallback fonts. These are yambar
|
||||
custom fallback fonts that will be searched before the fontconfig
|
||||
provided fallback list.
|
||||
| font-shaping
|
||||
: enum
|
||||
: no
|
||||
: Default setting for font-shaping, for use in particles. One of
|
||||
_full_ or _none_. When set to _full_ (the default), strings will be
|
||||
"shaped" using HarfBuzz. Requires support in fcft.
|
||||
| foreground
|
||||
: color
|
||||
: no
|
||||
|
@ -161,8 +172,8 @@ bar:
|
|||
|
||||
right:
|
||||
- clock:
|
||||
content:
|
||||
- string: {text: "{time}"}
|
||||
content:
|
||||
- string: {text: "{time}"}
|
||||
```
|
||||
|
||||
# FILES
|
||||
|
|
|
@ -46,82 +46,67 @@ bar:
|
|||
foreground: 000000ff
|
||||
deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]}
|
||||
- map: &i3_mode
|
||||
tag: mode
|
||||
default:
|
||||
- string:
|
||||
margin: 5
|
||||
text: "{mode}"
|
||||
deco: {background: {color: cc421dff}}
|
||||
- empty: {right-margin: 7}
|
||||
values:
|
||||
default: {empty: {}}
|
||||
conditions:
|
||||
mode == default: {empty: {}}
|
||||
content:
|
||||
"":
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*default, *focused]}}
|
||||
unfocused: {string: {<<: *default}}
|
||||
invisible: {string: {<<: [*default, *invisible]}}
|
||||
urgent: {string: {<<: [*default, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*default, *focused]}}
|
||||
state == unfocused: {string: {<<: *default}}
|
||||
state == invisible: {string: {<<: [*default, *invisible]}}
|
||||
state == urgent: {string: {<<: [*default, *urgent]}}
|
||||
main:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*main, *focused]}}
|
||||
unfocused: {string: {<<: *main}}
|
||||
invisible: {string: {<<: [*main, *invisible]}}
|
||||
urgent: {string: {<<: [*main, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*main, *focused]}}
|
||||
state == unfocused: {string: {<<: *main}}
|
||||
state == invisible: {string: {<<: [*main, *invisible]}}
|
||||
state == urgent: {string: {<<: [*main, *urgent]}}
|
||||
surfing:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*surfing, *focused]}}
|
||||
unfocused: {string: {<<: *surfing}}
|
||||
invisible: {string: {<<: [*surfing, *invisible]}}
|
||||
urgent: {string: {<<: [*surfing, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*surfing, *focused]}}
|
||||
state == unfocused: {string: {<<: *surfing}}
|
||||
state == invisible: {string: {<<: [*surfing, *invisible]}}
|
||||
state == urgent: {string: {<<: [*surfing, *urgent]}}
|
||||
misc:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*misc, *focused]}}
|
||||
unfocused: {string: {<<: *misc}}
|
||||
invisible: {string: {<<: [*misc, *invisible]}}
|
||||
urgent: {string: {<<: [*misc, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*misc, *focused]}}
|
||||
state == unfocused: {string: {<<: *misc}}
|
||||
state == invisible: {string: {<<: [*misc, *invisible]}}
|
||||
state == urgent: {string: {<<: [*misc, *urgent]}}
|
||||
|
||||
mail:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*mail, *focused]}}
|
||||
unfocused: {string: {<<: *mail}}
|
||||
invisible: {string: {<<: [*mail, *invisible]}}
|
||||
urgent: {string: {<<: [*mail, *urgent]}}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*mail, *focused]}}
|
||||
state == unfocused: {string: {<<: *mail}}
|
||||
state == invisible: {string: {<<: [*mail, *invisible]}}
|
||||
state == urgent: {string: {<<: [*mail, *urgent]}}
|
||||
music:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
focused: {string: {<<: [*music, *focused]}}
|
||||
unfocused: {string: {<<: *music}}
|
||||
invisible: {string: {<<: [*music, *invisible]}}
|
||||
urgent: {string: {<<: [*music, *urgent]}}
|
||||
current:
|
||||
map:
|
||||
left-margin: 7
|
||||
tag: application
|
||||
values:
|
||||
"":
|
||||
- map: {<<: *i3_mode}
|
||||
- string: {text: "{title}"}
|
||||
default:
|
||||
list:
|
||||
spacing: 0
|
||||
items:
|
||||
- map: {<<: *i3_mode}
|
||||
- string: {text: "{application}", max: 10, foreground: ffa0a0ff}
|
||||
- string: {text: ": "}
|
||||
- string: {text: "{title}", max: 35}
|
||||
conditions:
|
||||
state == focused: {string: {<<: [*music, *focused]}}
|
||||
state == unfocused: {string: {<<: *music}}
|
||||
state == invisible: {string: {<<: [*music, *invisible]}}
|
||||
state == urgent: {string: {<<: [*music, *urgent]}}
|
||||
|
||||
- foreign-toplevel:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
~activated: {empty: {}}
|
||||
activated:
|
||||
- string: {text: "{app-id}", foreground: ffa0a0ff}
|
||||
- string: {text: ": {title}"}
|
||||
center:
|
||||
- mpd:
|
||||
host: /run/mpd/socket
|
||||
|
@ -130,32 +115,28 @@ bar:
|
|||
spacing: 0
|
||||
items:
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{artist}"}}
|
||||
paused: {string: {text: "{artist}", foreground: ffffff66}}
|
||||
conditions:
|
||||
state == playing: {string: {text: "{artist}"}}
|
||||
state == paused: {string: {text: "{artist}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{album}"}}
|
||||
paused: {string: {text: "{album}", foreground: ffffff66}}
|
||||
conditions:
|
||||
state == playing: {string: {text: "{album}"}}
|
||||
state == paused: {string: {text: "{album}", foreground: ffffff66}}
|
||||
- string: {text: " | ", foreground: ffffff66}
|
||||
- map:
|
||||
tag: state
|
||||
values:
|
||||
playing: {string: {text: "{title}", foreground: ffa0a0ff}}
|
||||
paused: {string: {text: "{title}", foreground: ffffff66}}
|
||||
conditions:
|
||||
state == playing: {string: {text: "{title}", foreground: ffa0a0ff}}
|
||||
state == paused: {string: {text: "{title}", foreground: ffffff66}}
|
||||
|
||||
content:
|
||||
map:
|
||||
margin: 10
|
||||
tag: state
|
||||
values:
|
||||
offline: {string: {text: offline, foreground: ff0000ff}}
|
||||
stopped: {string: {text: stopped}}
|
||||
paused: {list: *artist_album_title}
|
||||
playing: {list: *artist_album_title}
|
||||
conditions:
|
||||
state == offline: {string: {text: offline, foreground: ff0000ff}}
|
||||
state == stopped: {string: {text: stopped}}
|
||||
state == paused: {list: *artist_album_title}
|
||||
state == playing: {list: *artist_album_title}
|
||||
|
||||
right:
|
||||
- removables:
|
||||
|
@ -165,24 +146,21 @@ bar:
|
|||
spacing: 5
|
||||
content:
|
||||
map:
|
||||
tag: mounted
|
||||
values:
|
||||
false:
|
||||
conditions:
|
||||
~mounted:
|
||||
map:
|
||||
tag: optical
|
||||
on-click: udisksctl mount -b {device}
|
||||
values:
|
||||
false: [{string: *drive}, {string: {text: "{label}"}}]
|
||||
true: [{string: *optical}, {string: {text: "{label}"}}]
|
||||
true:
|
||||
conditions:
|
||||
~optical: [{string: *drive}, {string: {text: "{label}"}}]
|
||||
optical: [{string: *optical}, {string: {text: "{label}"}}]
|
||||
mounted:
|
||||
map:
|
||||
tag: optical
|
||||
on-click: udisksctl unmount -b {device}
|
||||
values:
|
||||
false:
|
||||
conditions:
|
||||
~optical:
|
||||
- string: {<<: *drive, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
true:
|
||||
optical:
|
||||
- string: {<<: *optical, deco: *std_underline}
|
||||
- string: {text: "{label}"}
|
||||
- sway-xkb:
|
||||
|
@ -194,36 +172,31 @@ bar:
|
|||
name: enp1s0
|
||||
content:
|
||||
map:
|
||||
tag: carrier
|
||||
values:
|
||||
false: {empty: {}}
|
||||
true:
|
||||
conditions:
|
||||
~carrier: {empty: {}}
|
||||
carrier:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
up:
|
||||
conditions:
|
||||
state == up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default: {string: {text: , font: *awesome}}
|
||||
values:
|
||||
"": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
conditions:
|
||||
ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
- network:
|
||||
name: wlp2s0
|
||||
content:
|
||||
map:
|
||||
tag: state
|
||||
default: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
values:
|
||||
down: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
up:
|
||||
conditions:
|
||||
state == down: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
state == up:
|
||||
map:
|
||||
tag: ipv4
|
||||
default:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: "{ssid}"}
|
||||
values:
|
||||
"":
|
||||
conditions:
|
||||
ipv4 == "":
|
||||
- string: {text: , font: *awesome, foreground: ffffff66}
|
||||
- string: {text: "{ssid}", foreground: ffffff66}
|
||||
- alsa:
|
||||
|
@ -231,23 +204,19 @@ bar:
|
|||
mixer: Master
|
||||
content:
|
||||
map:
|
||||
tag: online
|
||||
values:
|
||||
false: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
true:
|
||||
conditions:
|
||||
~online: {string: {text: , font: *awesome, foreground: ff0000ff}}
|
||||
online:
|
||||
map:
|
||||
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
|
||||
tag: muted
|
||||
values:
|
||||
true: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
false:
|
||||
conditions:
|
||||
muted: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||
~muted:
|
||||
ramp:
|
||||
tag: volume
|
||||
items:
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- string: {text: , font: *awesome}
|
||||
- backlight:
|
||||
name: intel_backlight
|
||||
|
@ -255,11 +224,10 @@ bar:
|
|||
- battery:
|
||||
name: BAT0
|
||||
poll-interval: 30
|
||||
content:
|
||||
map:
|
||||
tag: state
|
||||
values:
|
||||
discharging:
|
||||
anchors:
|
||||
discharging: &discharging
|
||||
list:
|
||||
items:
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
|
@ -274,13 +242,20 @@ bar:
|
|||
- string: {text: , font: *awesome}
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
charging:
|
||||
content:
|
||||
map:
|
||||
conditions:
|
||||
state == unknown:
|
||||
<<: *discharging
|
||||
state == discharging:
|
||||
<<: *discharging
|
||||
state == charging:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% {estimate}"}
|
||||
full:
|
||||
state == full:
|
||||
- string: {text: , foreground: 00ff00ff, font: *awesome}
|
||||
- string: {text: "{capacity}% full"}
|
||||
not charging:
|
||||
state == not charging:
|
||||
- ramp:
|
||||
tag: capacity
|
||||
items:
|
||||
|
|
56
examples/configurations/river-tags.conf
Normal file
56
examples/configurations/river-tags.conf
Normal file
|
@ -0,0 +1,56 @@
|
|||
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:
|
||||
map:
|
||||
conditions:
|
||||
~occupied: {map: {<<: *river_base}}
|
||||
occupied: {map: {<<: *river_base, deco: *bg_default}}
|
||||
state == unfocused:
|
||||
map:
|
||||
<<: *river_base
|
||||
state == invisible:
|
||||
map:
|
||||
conditions:
|
||||
~occupied: {empty: {}}
|
||||
occupied: {map: {<<: *river_base, deco: {underline: {size: 3, color: ea6962ff}}}}
|
|
@ -127,7 +127,7 @@ while true; do
|
|||
inotifywait -qq --event modify "${fname}"
|
||||
|
||||
# Get info from the file
|
||||
output="$(tail -n4 "${fname}")"
|
||||
output="$(tail -n6 "${fname}")"
|
||||
title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )"
|
||||
#selmon="$(echo "${output}" | grep 'selmon')"
|
||||
layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )"
|
||||
|
|
7
font-shaping.h
Normal file
7
font-shaping.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
enum font_shaping {
|
||||
FONT_SHAPE_NONE,
|
||||
FONT_SHAPE_GRAPHEMES,
|
||||
FONT_SHAPE_FULL,
|
||||
};
|
4
log.c
4
log.c
|
@ -40,7 +40,9 @@ log_init(enum log_colorize _colorize, bool _do_syslog,
|
|||
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
|
||||
};
|
||||
|
||||
colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO);
|
||||
colorize = _colorize == LOG_COLORIZE_NEVER
|
||||
? false : _colorize == LOG_COLORIZE_ALWAYS
|
||||
? true : isatty(STDERR_FILENO);
|
||||
do_syslog = _do_syslog;
|
||||
log_level = _log_level;
|
||||
|
||||
|
|
5
main.c
5
main.c
|
@ -296,8 +296,9 @@ main(int argc, char *const *argv)
|
|||
"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);
|
||||
fcft_init((enum fcft_log_colorize)log_colorize, log_syslog,
|
||||
(enum fcft_log_class)log_level);
|
||||
atexit(&fcft_fini);
|
||||
|
||||
const struct sigaction sa = {.sa_handler = &signal_handler};
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
|
|
12
meson.build
12
meson.build
|
@ -1,7 +1,7 @@
|
|||
project('yambar', 'c',
|
||||
version: '1.7.0',
|
||||
version: '1.8.0',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.53.0',
|
||||
meson_version: '>=0.58.0',
|
||||
default_options: ['c_std=c18',
|
||||
'warning_level=1',
|
||||
'werror=true',
|
||||
|
@ -18,7 +18,7 @@ endif
|
|||
|
||||
# Compute the relative path used by compiler invocations.
|
||||
source_root = meson.current_source_dir().split('/')
|
||||
build_root = meson.build_root().split('/')
|
||||
build_root = meson.global_build_root().split('/')
|
||||
relative_dir_parts = []
|
||||
i = 0
|
||||
in_prefix = true
|
||||
|
@ -71,7 +71,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: ['>=2.4.0', '<3.0.0'], fallback: 'fcft')
|
||||
fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft')
|
||||
|
||||
add_project_arguments(
|
||||
['-D_GNU_SOURCE'] +
|
||||
|
@ -107,14 +107,16 @@ version = custom_target(
|
|||
'generate_version',
|
||||
build_always_stale: true,
|
||||
output: 'version.h',
|
||||
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@SOURCE_ROOT@', '@OUTPUT@'])
|
||||
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
|
||||
|
||||
yambar = executable(
|
||||
'yambar',
|
||||
'char32.c', 'char32.h',
|
||||
'color.h',
|
||||
'config-verify.c', 'config-verify.h',
|
||||
'config.c', 'config.h',
|
||||
'decoration.h',
|
||||
'font-shaping.h',
|
||||
'log.c', 'log.h',
|
||||
'main.c',
|
||||
'module.c', 'module.h',
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
@ -178,15 +179,24 @@ run(struct module *mod)
|
|||
|
||||
bar->refresh(bar);
|
||||
|
||||
int ret = 1;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = udev_monitor_get_fd(mon), .events = POLLIN},
|
||||
};
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
struct udev_device *dev = udev_monitor_receive_device(mon);
|
||||
if (dev == NULL)
|
||||
|
@ -209,7 +219,7 @@ run(struct module *mod)
|
|||
udev_unref(udev);
|
||||
|
||||
close(current_fd);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
|
|
|
@ -428,7 +428,15 @@ run(struct module *mod)
|
|||
{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = udev_monitor_get_fd(mon), .events = POLLIN},
|
||||
};
|
||||
poll(fds, 2, m->poll_interval > 0 ? m->poll_interval * 1000 : -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]),
|
||||
m->poll_interval > 0 ? m->poll_interval * 1000 : -1) < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
|
@ -496,7 +504,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_int},
|
||||
{"poll-interval", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/time.h>
|
||||
|
@ -67,7 +68,6 @@ content(struct module *mod)
|
|||
return exposable;
|
||||
}
|
||||
|
||||
#include <pthread.h>
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
|
@ -75,6 +75,8 @@ run(struct module *mod)
|
|||
const struct bar *bar = mod->bar;
|
||||
bar->refresh(bar);
|
||||
|
||||
int ret = 1;
|
||||
|
||||
while (true) {
|
||||
struct timespec _now;
|
||||
clock_gettime(CLOCK_REALTIME, &_now);
|
||||
|
@ -120,19 +122,28 @@ run(struct module *mod)
|
|||
now.tv_sec, now.tv_usec, timeout_ms);
|
||||
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
poll(fds, 1, timeout_ms);
|
||||
if (poll(fds, 1, timeout_ms) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
clock_new(struct particle *label, const char *date_format, const char *time_format, bool utc)
|
||||
clock_new(struct particle *label, const char *date_format,
|
||||
const char *time_format, bool utc)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
m->label = label;
|
||||
|
|
278
modules/cpu.c
Normal file
278
modules/cpu.c
Normal file
|
@ -0,0 +1,278 @@
|
|||
#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 <sys/sysinfo.h>
|
||||
|
||||
#define LOG_MODULE "cpu"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#define SMALLEST_INTERVAL 500
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
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 *label;
|
||||
uint16_t interval;
|
||||
struct cpu_stats cpu_stats;
|
||||
};
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
m->label->destroy(m->label);
|
||||
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(struct module *mod)
|
||||
{
|
||||
return "cpu";
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
get_cpu_nb_cores()
|
||||
{
|
||||
int nb_cores = get_nprocs();
|
||||
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)
|
||||
{
|
||||
uint32_t nb_cores = get_cpu_nb_cores();
|
||||
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", "r");
|
||||
if (NULL == fp) {
|
||||
LOG_ERRNO("unable to open /proc/stat");
|
||||
return;
|
||||
}
|
||||
|
||||
while ((read = getline(&line, &len, fp)) != -1 && core <= nb_cores) {
|
||||
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 *p = mod->private;
|
||||
uint32_t nb_cores = get_cpu_nb_cores();
|
||||
|
||||
char cpu_name[32];
|
||||
struct tag_set tags;
|
||||
tags.count = nb_cores + 1;
|
||||
tags.tags = calloc(tags.count, sizeof(*tags.tags));
|
||||
mtx_lock(&mod->lock);
|
||||
uint8_t cpu_usage = get_cpu_usage_percent(&p->cpu_stats, -1);
|
||||
tags.tags[0] = tag_new_int_range(mod, "cpu", cpu_usage, 0, 100);
|
||||
|
||||
for (uint32_t i = 0; i < nb_cores; ++i) {
|
||||
uint8_t cpu_usage = get_cpu_usage_percent(&p->cpu_stats, i);
|
||||
snprintf(cpu_name, sizeof(cpu_name), "cpu%u", i);
|
||||
tags.tags[i + 1] = tag_new_int_range(mod, cpu_name, cpu_usage, 0, 100);
|
||||
}
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
struct exposable *exposable = p->label->instantiate(p->label, &tags);
|
||||
|
||||
tag_set_destroy(&tags);
|
||||
free(tags.tags);
|
||||
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, 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);
|
||||
mtx_unlock(&mod->lock);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
cpu_new(uint16_t interval, struct particle *label)
|
||||
{
|
||||
struct private *p = calloc(1, sizeof(*p));
|
||||
p->label = label;
|
||||
uint32_t nb_cores = get_cpu_nb_cores();
|
||||
p->interval = interval;
|
||||
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, "interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return cpu_new(interval == NULL ? SMALLEST_INTERVAL : yml_value_as_int(interval), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < SMALLEST_INTERVAL) {
|
||||
LOG_ERR("%s: interval value cannot be less than %d ms", conf_err_prefix(chain, node), SMALLEST_INTERVAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"interval", false, &conf_verify_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
|
100
modules/i3.c
100
modules/i3.c
|
@ -37,6 +37,7 @@ struct workspace {
|
|||
bool visible;
|
||||
bool focused;
|
||||
bool urgent;
|
||||
bool empty;
|
||||
|
||||
struct {
|
||||
unsigned id;
|
||||
|
@ -59,6 +60,7 @@ struct private {
|
|||
size_t count;
|
||||
} ws_content;
|
||||
|
||||
bool strip_workspace_numbers;
|
||||
enum sort_mode sort_mode;
|
||||
tll(struct workspace) workspaces;
|
||||
|
||||
|
@ -70,6 +72,22 @@ 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;
|
||||
|
@ -89,10 +107,15 @@ workspace_from_json(const struct json_object *json, struct workspace *ws)
|
|||
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");
|
||||
LOG_ERR("workspace reply/event without 'name' and/or 'output' "
|
||||
"properties");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Sway only */
|
||||
struct json_object *focus = NULL;
|
||||
json_object_object_get_ex(json, "focus", &focus);
|
||||
|
||||
/* Optional */
|
||||
struct json_object *visible = NULL, *focused = NULL, *urgent = NULL;
|
||||
json_object_object_get_ex(json, "visible", &visible);
|
||||
|
@ -101,14 +124,22 @@ 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) {
|
||||
.name = strdup(name_as_string),
|
||||
.name_as_int = workspace_name_as_int(name_as_string),
|
||||
.name_as_int = name_as_int,
|
||||
.persistent = false,
|
||||
.output = strdup(json_object_get_string(output)),
|
||||
.visible = json_object_get_boolean(visible),
|
||||
.focused = json_object_get_boolean(focused),
|
||||
.urgent = json_object_get_boolean(urgent),
|
||||
.empty = is_empty,
|
||||
.window = {.title = NULL, .pid = -1},
|
||||
};
|
||||
|
||||
|
@ -353,6 +384,7 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
|||
else {
|
||||
workspace_free(ws);
|
||||
ws->name = strdup(current_name);
|
||||
ws->empty = true;
|
||||
assert(ws->persistent);
|
||||
}
|
||||
}
|
||||
|
@ -425,11 +457,12 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
}
|
||||
|
||||
const char *change_str = json_object_get_string(change);
|
||||
bool is_new = strcmp(change_str, "new") == 0;
|
||||
bool is_focus = strcmp(change_str, "focus") == 0;
|
||||
bool is_close = strcmp(change_str, "close") == 0;
|
||||
bool is_title = strcmp(change_str, "title") == 0;
|
||||
|
||||
if (!is_focus && !is_close && !is_title)
|
||||
if (!is_new && !is_focus && !is_close && !is_title)
|
||||
return true;
|
||||
|
||||
mtx_lock(&mod->lock);
|
||||
|
@ -454,12 +487,19 @@ handle_window_event(int type, const struct json_object *json, void *_mod)
|
|||
ws->window.title = ws->window.application = NULL;
|
||||
ws->window.pid = -1;
|
||||
|
||||
/* May not be true, but e.g. a subsequent “focus” event will
|
||||
* reset it... */
|
||||
ws->empty = true;
|
||||
|
||||
m->dirty = true;
|
||||
mtx_unlock(&mod->lock);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/* Non-close event - thus workspace cannot be empty */
|
||||
ws->empty = false;
|
||||
|
||||
struct json_object *container, *id, *name;
|
||||
if (!json_object_object_get_ex(json, "container", &container) ||
|
||||
!json_object_object_get_ex(container, "id", &id) ||
|
||||
|
@ -581,7 +621,7 @@ run(struct module *mod)
|
|||
if (!i3_get_socket_address(&addr))
|
||||
return 1;
|
||||
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (sock == -1) {
|
||||
LOG_ERRNO("failed to create UNIX socket");
|
||||
return 1;
|
||||
|
@ -598,10 +638,18 @@ 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 = {
|
||||
.name = strdup(name_as_string),
|
||||
.name_as_int = workspace_name_as_int(name_as_string),
|
||||
.name_as_int = name_as_int,
|
||||
.persistent = true,
|
||||
.empty = true,
|
||||
};
|
||||
workspace_add(m, ws);
|
||||
}
|
||||
|
@ -695,12 +743,33 @@ content(struct module *mod)
|
|||
ws->visible ? ws->focused ? "focused" : "unfocused" :
|
||||
"invisible";
|
||||
|
||||
LOG_DBG("%s: visible=%s, focused=%s, urgent=%s, empty=%s, state=%s, "
|
||||
"application=%s, title=%s, mode=%s",
|
||||
ws->name,
|
||||
ws->visible ? "yes" : "no",
|
||||
ws->focused ? "yes" : "no",
|
||||
ws->urgent ? "yes" : "no",
|
||||
ws->empty ? "yes" : "no",
|
||||
state,
|
||||
ws->window.application,
|
||||
ws->window.title,
|
||||
m->mode);
|
||||
|
||||
const char *name = ws->name;
|
||||
|
||||
if (m->strip_workspace_numbers) {
|
||||
const char *colon = strchr(name, ':');
|
||||
if (colon != NULL)
|
||||
name = colon + 1;
|
||||
}
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_string(mod, "name", ws->name),
|
||||
tag_new_string(mod, "name", name),
|
||||
tag_new_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),
|
||||
|
@ -708,7 +777,7 @@ content(struct module *mod)
|
|||
|
||||
tag_new_string(mod, "mode", m->mode),
|
||||
},
|
||||
.count = 8,
|
||||
.count = 9,
|
||||
};
|
||||
|
||||
if (ws->focused) {
|
||||
|
@ -747,7 +816,8 @@ 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])
|
||||
const char *persistent_workspaces[static persistent_count],
|
||||
bool strip_workspace_numbers)
|
||||
{
|
||||
struct private *m = calloc(1, sizeof(*m));
|
||||
|
||||
|
@ -763,6 +833,7 @@ i3_new(struct i3_workspaces workspaces[], size_t workspace_count,
|
|||
m->ws_content.v[i].content = workspaces[i].content;
|
||||
}
|
||||
|
||||
m->strip_workspace_numbers = strip_workspace_numbers;
|
||||
m->sort_mode = sort_mode;
|
||||
|
||||
m->persistent_count = persistent_count;
|
||||
|
@ -790,6 +861,8 @@ 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;
|
||||
|
@ -828,7 +901,9 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
|||
}
|
||||
|
||||
return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode,
|
||||
persistent_count, persistent_workspaces);
|
||||
persistent_count, persistent_workspaces,
|
||||
(strip_workspace_number != NULL
|
||||
? yml_value_as_bool(strip_workspace_number) : false));
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -878,11 +953,12 @@ static bool
|
|||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
{"sort", false, &verify_sort},
|
||||
{"persistent", false, &verify_persistent},
|
||||
{"strip-workspace-numbers", false, &conf_verify_bool},
|
||||
{"content", true, &verify_content},
|
||||
{"anchors", false, NULL},
|
||||
{NULL, false, NULL},
|
||||
|
|
192
modules/mem.c
Normal file
192
modules/mem.c
Normal file
|
@ -0,0 +1,192 @@
|
|||
#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
|
||||
#define SMALLEST_INTERVAL 500
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
uint16_t interval;
|
||||
};
|
||||
|
||||
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(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", "r");
|
||||
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;
|
||||
uint64_t mem_free = 0;
|
||||
uint64_t mem_used = 0;
|
||||
uint64_t mem_total = 0;
|
||||
|
||||
if (!get_mem_stats(&mem_free, &mem_total)) {
|
||||
LOG_ERR("unable to retrieve the memory stats");
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
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, "interval");
|
||||
const struct yml_node *c = yml_get_value(node, "content");
|
||||
|
||||
return mem_new(interval == NULL ? SMALLEST_INTERVAL : yml_value_as_int(interval), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
conf_verify_interval(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!conf_verify_unsigned(chain, node))
|
||||
return false;
|
||||
|
||||
if (yml_value_as_int(node) < SMALLEST_INTERVAL) {
|
||||
LOG_ERR("%s: interval value cannot be less than %d ms", conf_err_prefix(chain, node), SMALLEST_INTERVAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"interval", false, &conf_verify_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
|
|
@ -17,6 +17,8 @@ mod_data = {
|
|||
'backlight': [[], [m, udev]],
|
||||
'battery': [[], [udev]],
|
||||
'clock': [[], []],
|
||||
'cpu': [[], []],
|
||||
'mem': [[], []],
|
||||
'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||
'label': [[], []],
|
||||
'network': [[], []],
|
||||
|
|
|
@ -223,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, 0);
|
||||
int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
|
||||
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
||||
strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1);
|
||||
|
@ -251,7 +251,13 @@ wait_for_socket_create(const struct module *mod)
|
|||
{.fd = fd, .events = POLLIN}
|
||||
};
|
||||
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = true;
|
||||
|
@ -377,8 +383,9 @@ run(struct module *mod)
|
|||
struct private *m = mod->private;
|
||||
|
||||
bool aborted = false;
|
||||
int ret = 0;
|
||||
|
||||
while (!aborted) {
|
||||
while (!aborted && ret == 0) {
|
||||
|
||||
if (m->conn != NULL) {
|
||||
mpd_connection_free(m->conn);
|
||||
|
@ -396,7 +403,7 @@ run(struct module *mod)
|
|||
mtx_unlock(&mod->lock);
|
||||
|
||||
/* Keep trying to connect, until we succeed */
|
||||
while (!aborted) {
|
||||
while (!aborted && ret == 0) {
|
||||
if (m->port == 0) {
|
||||
/* Use inotify to watch for socket creation */
|
||||
aborted = wait_for_socket_create(mod);
|
||||
|
@ -414,16 +421,27 @@ run(struct module *mod)
|
|||
* host), wait for a while until we try to re-connect
|
||||
* again.
|
||||
*/
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
int res = poll(fds, 1, 10 * 1000);
|
||||
while (!aborted) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 10 * 1000);
|
||||
|
||||
if (res == 1) {
|
||||
assert(fds[0].revents & POLLIN);
|
||||
aborted = true;
|
||||
if (res < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (res == 1) {
|
||||
assert(fds[0].revents & POLLIN);
|
||||
aborted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aborted)
|
||||
if (aborted || ret != 0)
|
||||
break;
|
||||
|
||||
/* Initial state (after establishing a connection) */
|
||||
|
@ -446,7 +464,14 @@ run(struct module *mod)
|
|||
break;
|
||||
}
|
||||
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
aborted = true;
|
||||
|
@ -477,7 +502,7 @@ run(struct module *mod)
|
|||
m->conn = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return aborted ? 0 : ret;
|
||||
}
|
||||
|
||||
struct refresh_context {
|
||||
|
@ -616,7 +641,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"host", true, &conf_verify_string},
|
||||
{"port", false, &conf_verify_int},
|
||||
{"port", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ nl_pid_value(void)
|
|||
static int
|
||||
netlink_connect_rt(void)
|
||||
{
|
||||
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
|
||||
if (sock == -1) {
|
||||
LOG_ERRNO("failed to create netlink socket");
|
||||
return -1;
|
||||
|
@ -191,7 +191,7 @@ netlink_connect_rt(void)
|
|||
static int
|
||||
netlink_connect_genl(void)
|
||||
{
|
||||
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
|
||||
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC);
|
||||
if (sock == -1) {
|
||||
LOG_ERRNO("failed to create netlink socket");
|
||||
return -1;
|
||||
|
@ -310,6 +310,9 @@ send_nl80211_request(struct private *m, uint8_t cmd, uint16_t flags, uint32_t se
|
|||
if (m->ifindex < 0)
|
||||
return false;
|
||||
|
||||
if (m->nl80211.family_id == (uint16_t)-1)
|
||||
return false;
|
||||
|
||||
const struct {
|
||||
struct nlmsghdr hdr;
|
||||
struct {
|
||||
|
@ -692,6 +695,7 @@ handle_genl_ctrl(struct module *mod, uint16_t type, bool nested,
|
|||
case CTRL_ATTR_FAMILY_ID: {
|
||||
m->nl80211.family_id = *(const uint16_t *)payload;
|
||||
send_nl80211_get_interface(m);
|
||||
send_nl80211_get_station(m);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1253,7 +1257,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_int},
|
||||
{"poll-interval", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -35,8 +36,8 @@ struct partition {
|
|||
char *label;
|
||||
|
||||
uint64_t size;
|
||||
bool audio_cd;
|
||||
|
||||
/*tll(char *) mount_points;*/
|
||||
mount_point_list_t mount_points;
|
||||
};
|
||||
|
||||
|
@ -141,13 +142,14 @@ 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 = 8,
|
||||
.count = 9,
|
||||
};
|
||||
|
||||
exposables[idx++] = m->label->instantiate(m->label, &tags);
|
||||
|
@ -162,11 +164,17 @@ content(struct module *mod)
|
|||
static void
|
||||
find_mount_points(const char *dev_path, mount_point_list_t *mount_points)
|
||||
{
|
||||
FILE *f = fopen("/proc/self/mountinfo", "r");
|
||||
assert(f != NULL);
|
||||
int fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC);
|
||||
FILE *f = fd >= 0 ? fdopen(fd, "r") : NULL;
|
||||
|
||||
if (fd < 0 || f == NULL) {
|
||||
LOG_ERRNO("failed to open /proc/self/mountinfo");
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
char line[4096];
|
||||
|
||||
while (fgets(line, sizeof(line), f) != NULL) {
|
||||
char *dev = NULL, *path = NULL;
|
||||
|
||||
|
@ -277,6 +285,64 @@ add_partition(struct module *mod, struct block_device *block,
|
|||
.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()}));
|
||||
|
||||
struct partition *p = &tll_back(block->partitions);
|
||||
|
@ -295,7 +361,9 @@ del_partition(struct module *mod, struct block_device *block,
|
|||
|
||||
tll_foreach(block->partitions, it) {
|
||||
if (strcmp(it->item.sys_path, sys_path) == 0) {
|
||||
LOG_INFO("partition: del: %s", it->item.dev_path);
|
||||
LOG_INFO("%s: del: %s",
|
||||
it->item.audio_cd ? "audio CD" : "partition",
|
||||
it->item.dev_path);
|
||||
|
||||
free_partition(&it->item);
|
||||
tll_remove(block->partitions, it);
|
||||
|
@ -350,8 +418,16 @@ 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 media = _fs_usage != NULL && strcmp(_fs_usage, "filesystem") == 0;
|
||||
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;
|
||||
|
||||
LOG_DBG("device: add: %s: vendor=%s, model=%s, optical=%d, size=%"PRIu64,
|
||||
udev_device_get_devnode(dev), vendor, model, optical, size);
|
||||
|
@ -373,8 +449,12 @@ add_device(struct module *mod, struct udev_device *dev)
|
|||
mtx_unlock(&mod->lock);
|
||||
|
||||
struct block_device *block = &tll_back(m->devices);
|
||||
if (optical && media)
|
||||
add_partition(mod, block, dev);
|
||||
if (optical) {
|
||||
if (have_fs)
|
||||
add_partition(mod, block, dev);
|
||||
else if (audio_track_count > 0)
|
||||
add_audio_cd(mod, block, dev);
|
||||
}
|
||||
|
||||
return &tll_back(m->devices);
|
||||
}
|
||||
|
@ -408,31 +488,53 @@ 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) {
|
||||
if (strcmp(it->item.sys_path, sys_path) == 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
block = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -545,7 +647,9 @@ 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);
|
||||
int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC);
|
||||
|
||||
int ret = 1;
|
||||
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
|
@ -553,10 +657,18 @@ run(struct module *mod)
|
|||
{.fd = udev_monitor_get_fd(dev_mon), .events = POLLIN},
|
||||
{.fd = mount_info_fd, .events = POLLPRI},
|
||||
};
|
||||
poll(fds, 3, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bool update = false;
|
||||
|
||||
|
@ -587,7 +699,7 @@ run(struct module *mod)
|
|||
|
||||
udev_monitor_unref(dev_mon);
|
||||
udev_unref(udev);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
|
@ -652,9 +764,9 @@ static bool
|
|||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
{"ignore", false, &verify_ignore},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
|
|
@ -396,7 +396,7 @@ execute_script(struct module *mod)
|
|||
|
||||
/* Stdout redirection pipe */
|
||||
int comm_pipe[2];
|
||||
if (pipe(comm_pipe) < 0) {
|
||||
if (pipe2(comm_pipe, O_CLOEXEC) < 0) {
|
||||
LOG_ERRNO("failed to create stdin/stdout redirection pipe");
|
||||
close(exec_pipe[0]);
|
||||
close(exec_pipe[1]);
|
||||
|
@ -444,7 +444,7 @@ execute_script(struct module *mod)
|
|||
close(comm_pipe[0]);
|
||||
|
||||
/* Re-direct stdin/stdout */
|
||||
int dev_null = open("/dev/null", O_RDONLY);
|
||||
int dev_null = open("/dev/null", O_RDONLY | O_CLOEXEC);
|
||||
if (dev_null < 0)
|
||||
goto fail;
|
||||
|
||||
|
@ -458,16 +458,6 @@ 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:
|
||||
|
@ -709,7 +699,7 @@ 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_int},
|
||||
{"poll-interval", false, &conf_verify_unsigned},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
|
@ -267,7 +267,7 @@ run(struct module *mod)
|
|||
if (!i3_get_socket_address(&addr))
|
||||
return 1;
|
||||
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (sock == -1) {
|
||||
LOG_ERRNO("failed to create UNIX socket");
|
||||
return 1;
|
||||
|
@ -374,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_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
{"identifiers", true, &verify_identifiers},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
|
|
@ -399,7 +399,14 @@ event_loop(struct module *mod, xcb_connection_t *conn, int xkb_event_base)
|
|||
};
|
||||
|
||||
/* Use poll() since xcb_wait_for_events() doesn't return on signals */
|
||||
poll(pfds, sizeof(pfds) / sizeof(pfds[0]), -1);
|
||||
if (poll(pfds, sizeof(pfds) / sizeof(pfds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (pfds[0].revents & POLLIN) {
|
||||
ret = true;
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <threads.h>
|
||||
#include <libgen.h>
|
||||
|
@ -247,14 +248,24 @@ run(struct module *mod)
|
|||
update_title(mod);
|
||||
mod->bar->refresh(mod->bar);
|
||||
|
||||
int ret = 1;
|
||||
|
||||
int xcb_fd = xcb_get_file_descriptor(m->conn);
|
||||
while (true) {
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN},
|
||||
{.fd = xcb_fd, .events = POLLIN}};
|
||||
poll(fds, 2, -1);
|
||||
if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
for (xcb_generic_event_t *_e = xcb_wait_for_event(m->conn);
|
||||
_e != NULL;
|
||||
|
@ -293,7 +304,7 @@ run(struct module *mod)
|
|||
|
||||
xcb_destroy_window(m->conn, m->monitor_win);
|
||||
xcb_disconnect(m->conn);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
|
|
10
particle.c
10
particle.c
|
@ -31,14 +31,15 @@ particle_default_destroy(struct particle *particle)
|
|||
struct particle *
|
||||
particle_common_new(int left_margin, int right_margin,
|
||||
const char **on_click_templates,
|
||||
struct fcft_font *font, pixman_color_t foreground,
|
||||
struct deco *deco)
|
||||
struct fcft_font *font, enum font_shaping font_shaping,
|
||||
pixman_color_t foreground, struct deco *deco)
|
||||
{
|
||||
struct particle *p = calloc(1, sizeof(*p));
|
||||
p->left_margin = left_margin;
|
||||
p->right_margin = right_margin;
|
||||
p->foreground = foreground;
|
||||
p->font = font;
|
||||
p->font_shaping = font_shaping;
|
||||
p->deco = deco;
|
||||
|
||||
if (on_click_templates != NULL) {
|
||||
|
@ -276,11 +277,6 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
/* Close *all* other FDs (e.g. script modules' FDs) */
|
||||
for (int i = STDERR_FILENO + 1; i < 65536; i++)
|
||||
if (i != pipe_fds[1])
|
||||
close(i);
|
||||
|
||||
execvp(argv[0], argv);
|
||||
|
||||
fail:
|
||||
|
|
22
particle.h
22
particle.h
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "color.h"
|
||||
#include "decoration.h"
|
||||
#include "font-shaping.h"
|
||||
#include "tag.h"
|
||||
|
||||
enum mouse_event {
|
||||
|
@ -35,6 +36,7 @@ struct particle {
|
|||
|
||||
pixman_color_t foreground;
|
||||
struct fcft_font *font;
|
||||
enum font_shaping font_shaping;
|
||||
struct deco *deco;
|
||||
|
||||
void (*destroy)(struct particle *particle);
|
||||
|
@ -61,7 +63,8 @@ struct exposable {
|
|||
|
||||
struct particle *particle_common_new(
|
||||
int left_margin, int right_margin, const char *on_click_templates[],
|
||||
struct fcft_font *font, pixman_color_t foreground, struct deco *deco);
|
||||
struct fcft_font *font, enum font_shaping font_shaping,
|
||||
pixman_color_t foreground, struct deco *deco);
|
||||
|
||||
void particle_default_destroy(struct particle *particle);
|
||||
|
||||
|
@ -76,12 +79,13 @@ void exposable_default_on_mouse(
|
|||
enum mouse_event event, enum mouse_button btn, int x, int y);
|
||||
|
||||
/* List of attributes *all* particles implement */
|
||||
#define PARTICLE_COMMON_ATTRS \
|
||||
{"margin", false, &conf_verify_int}, \
|
||||
{"left-margin", false, &conf_verify_int}, \
|
||||
{"right-margin", false, &conf_verify_int}, \
|
||||
{"on-click", false, &conf_verify_on_click}, \
|
||||
{"font", false, &conf_verify_font}, \
|
||||
{"foreground", false, &conf_verify_color}, \
|
||||
{"deco", false, &conf_verify_decoration}, \
|
||||
#define PARTICLE_COMMON_ATTRS \
|
||||
{"margin", false, &conf_verify_unsigned}, \
|
||||
{"left-margin", false, &conf_verify_unsigned}, \
|
||||
{"right-margin", false, &conf_verify_unsigned}, \
|
||||
{"on-click", false, &conf_verify_on_click}, \
|
||||
{"font", false, &conf_verify_font}, \
|
||||
{"font-shaping", false, &conf_verify_font_shaping}, \
|
||||
{"foreground", false, &conf_verify_color}, \
|
||||
{"deco", false, &conf_verify_decoration}, \
|
||||
{NULL, false, NULL}
|
||||
|
|
|
@ -197,7 +197,7 @@ from_conf(const struct yml_node *node, struct particle *common)
|
|||
yml_list_next(&it), idx++)
|
||||
{
|
||||
parts[idx] = conf_to_particle(
|
||||
it.node, (struct conf_inherit){common->font, common->foreground});
|
||||
it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground});
|
||||
}
|
||||
|
||||
return particle_list_new(common, parts, count, left_spacing, right_spacing);
|
||||
|
@ -208,9 +208,9 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"items", true, &conf_verify_particle_list_items},
|
||||
{"spacing", false, &conf_verify_int},
|
||||
{"left-spacing", false, &conf_verify_int},
|
||||
{"right-spacing", false, &conf_verify_int},
|
||||
{"spacing", false, &conf_verify_unsigned},
|
||||
{"left-spacing", false, &conf_verify_unsigned},
|
||||
{"right-spacing", false, &conf_verify_unsigned},
|
||||
PARTICLE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
360
particles/map.c
360
particles/map.c
|
@ -1,5 +1,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define LOG_MODULE "map"
|
||||
|
@ -10,13 +11,228 @@
|
|||
#include "../plugin.h"
|
||||
#include "dynlist.h"
|
||||
|
||||
enum map_op{
|
||||
MAP_OP_EQ,
|
||||
MAP_OP_NE,
|
||||
MAP_OP_LE,
|
||||
MAP_OP_LT,
|
||||
MAP_OP_GE,
|
||||
MAP_OP_GT,
|
||||
/* The next two are for bool types */
|
||||
MAP_OP_SELF,
|
||||
MAP_OP_NOT,
|
||||
};
|
||||
|
||||
struct map_condition {
|
||||
char *tag;
|
||||
enum map_op op;
|
||||
char *value;
|
||||
};
|
||||
|
||||
static char *
|
||||
trim(char *s)
|
||||
{
|
||||
while (*s == ' ')
|
||||
s++;
|
||||
|
||||
char *end = s + strlen(s) - 1;
|
||||
while (*end == ' ') {
|
||||
*end = '\0';
|
||||
end--;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
bool
|
||||
int_condition(const long tag_value, const long cond_value, enum map_op op)
|
||||
{
|
||||
switch (op) {
|
||||
case MAP_OP_EQ: return tag_value == cond_value;
|
||||
case MAP_OP_NE: return tag_value != cond_value;
|
||||
case MAP_OP_LE: return tag_value <= cond_value;
|
||||
case MAP_OP_LT: return tag_value < cond_value;
|
||||
case MAP_OP_GE: return tag_value >= cond_value;
|
||||
case MAP_OP_GT: return tag_value > cond_value;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
float_condition(const double tag_value, const double cond_value, enum map_op op)
|
||||
{
|
||||
switch (op) {
|
||||
case MAP_OP_EQ: return tag_value == cond_value;
|
||||
case MAP_OP_NE: return tag_value != cond_value;
|
||||
case MAP_OP_LE: return tag_value <= cond_value;
|
||||
case MAP_OP_LT: return tag_value < cond_value;
|
||||
case MAP_OP_GE: return tag_value >= cond_value;
|
||||
case MAP_OP_GT: return tag_value > cond_value;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
str_condition(const char* tag_value, const char* cond_value, enum map_op op)
|
||||
{
|
||||
switch (op) {
|
||||
case MAP_OP_EQ: return strcmp(tag_value, cond_value) == 0;
|
||||
case MAP_OP_NE: return strcmp(tag_value, cond_value) != 0;
|
||||
case MAP_OP_LE: return strcmp(tag_value, cond_value) <= 0;
|
||||
case MAP_OP_LT: return strcmp(tag_value, cond_value) < 0;
|
||||
case MAP_OP_GE: return strcmp(tag_value, cond_value) >= 0;
|
||||
case MAP_OP_GT: return strcmp(tag_value, cond_value) > 0;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
eval_map_condition(const struct map_condition* map_cond, const struct tag_set *tags)
|
||||
{
|
||||
const struct tag *tag = tag_for_name(tags, map_cond->tag);
|
||||
if (tag == NULL) {
|
||||
LOG_WARN("tag %s not found", map_cond->tag);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (tag->type(tag)) {
|
||||
case TAG_TYPE_INT: {
|
||||
errno = 0;
|
||||
char *end;
|
||||
const long cond_value = strtol(map_cond->value, &end, 0);
|
||||
|
||||
if (errno == ERANGE) {
|
||||
LOG_WARN("value %s is too large", map_cond->value);
|
||||
return false;
|
||||
} else if (*end != '\0') {
|
||||
LOG_WARN("failed to parse %s into int", map_cond->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
const long tag_value = tag->as_int(tag);
|
||||
return int_condition(tag_value, cond_value, map_cond->op);
|
||||
}
|
||||
case TAG_TYPE_FLOAT: {
|
||||
errno = 0;
|
||||
char *end;
|
||||
const double cond_value = strtod(map_cond->value, &end);
|
||||
|
||||
if (errno == ERANGE) {
|
||||
LOG_WARN("value %s is too large", map_cond->value);
|
||||
return false;
|
||||
} else if (*end != '\0') {
|
||||
LOG_WARN("failed to parse %s into float", map_cond->value);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double tag_value = tag->as_float(tag);
|
||||
return float_condition(tag_value, cond_value, map_cond->op);
|
||||
}
|
||||
case TAG_TYPE_BOOL:
|
||||
if (map_cond->op == MAP_OP_SELF)
|
||||
return tag->as_bool(tag);
|
||||
else if (map_cond->op == MAP_OP_NOT)
|
||||
return !tag->as_bool(tag);
|
||||
else {
|
||||
LOG_WARN("boolean tag '%s' should be used directly", map_cond->tag);
|
||||
return false;
|
||||
}
|
||||
case TAG_TYPE_STRING: {
|
||||
const char* tag_value = tag->as_string(tag);
|
||||
return str_condition(tag_value, map_cond->value, map_cond->op);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct map_condition*
|
||||
map_condition_from_str(const char *str)
|
||||
{
|
||||
struct map_condition *cond = malloc(sizeof(*cond));
|
||||
|
||||
/* This strdup is just to discard the 'const' qualifier */
|
||||
char *str_cpy = strdup(str);
|
||||
char *op_str = strpbrk(str_cpy, "=!<>~");
|
||||
|
||||
/* we've already checked things in the verify functions, so these should all work */
|
||||
char *tag = str_cpy;
|
||||
char *value = NULL;
|
||||
|
||||
if (op_str == NULL) {
|
||||
cond->tag = strdup(trim(tag));
|
||||
cond->op = MAP_OP_SELF;
|
||||
cond->value = NULL;
|
||||
free(str_cpy);
|
||||
return cond;
|
||||
}
|
||||
|
||||
switch (op_str[0]) {
|
||||
case '=':
|
||||
cond->op = MAP_OP_EQ;
|
||||
value = op_str + 2;
|
||||
break;
|
||||
case '!':
|
||||
cond->op = MAP_OP_NE;
|
||||
value = op_str + 2;
|
||||
break;
|
||||
case '<':
|
||||
if (op_str[1] == '=') {
|
||||
cond->op = MAP_OP_LE;
|
||||
value = op_str + 2;
|
||||
} else {
|
||||
cond->op = MAP_OP_LT;
|
||||
value = op_str + 1;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if (op_str[1] == '=') {
|
||||
cond->op = MAP_OP_GE;
|
||||
value = op_str + 2;
|
||||
} else {
|
||||
cond->op = MAP_OP_GT;
|
||||
value = op_str + 1;
|
||||
}
|
||||
break;
|
||||
case '~':
|
||||
tag = op_str + 1;
|
||||
cond->op = MAP_OP_NOT;
|
||||
break;
|
||||
}
|
||||
|
||||
/* NULL terminate the tag value */
|
||||
op_str[0] = '\0';
|
||||
|
||||
cond->tag = strdup(trim(tag));
|
||||
|
||||
cond->value = NULL;
|
||||
if (value != NULL) {
|
||||
value = trim(value);
|
||||
const size_t value_len = strlen(value);
|
||||
if (value[0] == '"' && value[value_len - 1] == '"') {
|
||||
value[value_len - 1] = '\0';
|
||||
++value;
|
||||
}
|
||||
cond->value = strdup(value);
|
||||
}
|
||||
|
||||
free(str_cpy);
|
||||
return cond;
|
||||
}
|
||||
|
||||
void
|
||||
free_map_condition(struct map_condition* mc)
|
||||
{
|
||||
free(mc->tag);
|
||||
free(mc->value);
|
||||
free(mc);
|
||||
}
|
||||
|
||||
struct particle_map {
|
||||
const char *tag_value;
|
||||
struct map_condition *condition;
|
||||
struct particle *particle;
|
||||
};
|
||||
|
||||
struct private {
|
||||
char *tag;
|
||||
struct particle *default_particle;
|
||||
struct particle_map *map;
|
||||
size_t count;
|
||||
|
@ -92,21 +308,12 @@ static struct exposable *
|
|||
instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||
{
|
||||
const struct private *p = particle->private;
|
||||
const struct tag *tag = tag_for_name(tags, p->tag);
|
||||
|
||||
if (tag == NULL) {
|
||||
return p->default_particle != NULL
|
||||
? p->default_particle->instantiate(p->default_particle, tags)
|
||||
: dynlist_exposable_new(NULL, 0, 0, 0);
|
||||
}
|
||||
|
||||
const char *tag_value = tag->as_string(tag);
|
||||
struct particle *pp = NULL;
|
||||
|
||||
for (size_t i = 0; i < p->count; i++) {
|
||||
const struct particle_map *e = &p->map[i];
|
||||
|
||||
if (strcmp(e->tag_value, tag_value) != 0)
|
||||
if (!eval_map_condition(e->condition, tags))
|
||||
continue;
|
||||
|
||||
pp = e->particle;
|
||||
|
@ -144,28 +351,25 @@ particle_destroy(struct particle *particle)
|
|||
for (size_t i = 0; i < p->count; i++) {
|
||||
struct particle *pp = p->map[i].particle;
|
||||
pp->destroy(pp);
|
||||
free((char *)p->map[i].tag_value);
|
||||
free_map_condition(p->map[i].condition);
|
||||
}
|
||||
|
||||
free(p->map);
|
||||
free(p->tag);
|
||||
free(p);
|
||||
particle_default_destroy(particle);
|
||||
}
|
||||
|
||||
static struct particle *
|
||||
map_new(struct particle *common, const char *tag,
|
||||
const struct particle_map particle_map[], size_t count,
|
||||
struct particle *default_particle)
|
||||
map_new(struct particle *common, const struct particle_map particle_map[],
|
||||
size_t count, struct particle *default_particle)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->tag = strdup(tag);
|
||||
priv->default_particle = default_particle;
|
||||
priv->count = count;
|
||||
priv->map = malloc(count * sizeof(priv->map[0]));
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
priv->map[i].tag_value = strdup(particle_map[i].tag_value);
|
||||
priv->map[i].condition = particle_map[i].condition;
|
||||
priv->map[i].particle = particle_map[i].particle;
|
||||
}
|
||||
|
||||
|
@ -176,7 +380,103 @@ map_new(struct particle *common, const char *tag,
|
|||
}
|
||||
|
||||
static bool
|
||||
verify_map_values(keychain_t *chain, const struct yml_node *node)
|
||||
verify_map_condition_syntax(keychain_t *chain, const struct yml_node *node,
|
||||
const char *line)
|
||||
{
|
||||
/* We need this strdup to discard the 'const' qualifier */
|
||||
char *cond = strdup(line);
|
||||
char *op_str = strpbrk(cond, " =!<>~");
|
||||
if (op_str == NULL) {
|
||||
char *tag = trim(cond);
|
||||
if (tag[0] == '\0')
|
||||
goto syntax_fail_missing_tag;
|
||||
|
||||
free(cond);
|
||||
return true;
|
||||
}
|
||||
op_str = trim(op_str);
|
||||
|
||||
char *tag = cond;
|
||||
char *value = NULL;
|
||||
|
||||
switch (op_str[0]) {
|
||||
case '=':
|
||||
if (op_str[1] != '=')
|
||||
goto syntax_fail_invalid_op;
|
||||
|
||||
value = op_str + 2;
|
||||
break;
|
||||
|
||||
case '!':
|
||||
if (op_str[1] != '=')
|
||||
goto syntax_fail_invalid_op;
|
||||
|
||||
value = op_str + 2;
|
||||
break;
|
||||
|
||||
case '<':
|
||||
if (op_str[1] == '=')
|
||||
value = op_str + 2;
|
||||
else
|
||||
value = op_str + 1;
|
||||
break;
|
||||
|
||||
case '>':
|
||||
if (op_str[1] == '=')
|
||||
value = op_str + 2;
|
||||
else
|
||||
value = op_str + 1;
|
||||
break;
|
||||
|
||||
case '~':
|
||||
tag = op_str + 1;
|
||||
if (strpbrk(tag, " =!<>~") != NULL)
|
||||
goto syntax_fail_bad_not;
|
||||
value = NULL;
|
||||
break;
|
||||
default:
|
||||
goto syntax_fail_invalid_op;
|
||||
}
|
||||
|
||||
/* NULL terminate the tag value */
|
||||
op_str[0] = '\0';
|
||||
|
||||
tag = trim(tag);
|
||||
if (tag[0] == '\0')
|
||||
goto syntax_fail_missing_tag;
|
||||
|
||||
value = value != NULL ? trim(value) : NULL;
|
||||
|
||||
if (value != NULL && value[0] == '\0')
|
||||
goto syntax_fail_missing_value;
|
||||
|
||||
free(cond);
|
||||
return true;
|
||||
|
||||
syntax_fail_invalid_op:
|
||||
LOG_ERR("%s: \"%s\" invalid operator", conf_err_prefix(chain, node), line);
|
||||
goto err;
|
||||
|
||||
syntax_fail_missing_tag:
|
||||
LOG_ERR("%s: \"%s\" missing tag", conf_err_prefix(chain, node), line);
|
||||
goto err;
|
||||
|
||||
syntax_fail_missing_value:
|
||||
LOG_ERR("%s: \"%s\" missing value", conf_err_prefix(chain, node), line);
|
||||
goto err;
|
||||
|
||||
syntax_fail_bad_not:
|
||||
LOG_ERR("%s: \"%s\" '~' cannot be used with other operators",
|
||||
conf_err_prefix(chain, node), line);
|
||||
goto err;
|
||||
|
||||
err:
|
||||
free(cond);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_map_conditions(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
if (!yml_is_dict(node)) {
|
||||
LOG_ERR(
|
||||
|
@ -195,6 +495,9 @@ verify_map_values(keychain_t *chain, const struct yml_node *node)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!verify_map_condition_syntax(chain, it.key, key))
|
||||
return false;
|
||||
|
||||
if (!conf_verify_particle(chain_push(chain, key), it.value))
|
||||
return false;
|
||||
|
||||
|
@ -207,40 +510,37 @@ verify_map_values(keychain_t *chain, const struct yml_node *node)
|
|||
static struct particle *
|
||||
from_conf(const struct yml_node *node, struct particle *common)
|
||||
{
|
||||
const struct yml_node *tag = yml_get_value(node, "tag");
|
||||
const struct yml_node *values = yml_get_value(node, "values");
|
||||
const struct yml_node *conditions = yml_get_value(node, "conditions");
|
||||
const struct yml_node *def = yml_get_value(node, "default");
|
||||
|
||||
struct particle_map particle_map[yml_dict_length(values)];
|
||||
struct particle_map particle_map[yml_dict_length(conditions)];
|
||||
|
||||
struct conf_inherit inherited = {
|
||||
.font = common->font,
|
||||
.font_shaping = common->font_shaping,
|
||||
.foreground = common->foreground
|
||||
};
|
||||
|
||||
size_t idx = 0;
|
||||
for (struct yml_dict_iter it = yml_dict_iter(values);
|
||||
for (struct yml_dict_iter it = yml_dict_iter(conditions);
|
||||
it.key != NULL;
|
||||
yml_dict_next(&it), idx++)
|
||||
{
|
||||
particle_map[idx].tag_value = yml_value_as_string(it.key);
|
||||
particle_map[idx].condition = map_condition_from_str(yml_value_as_string(it.key));
|
||||
particle_map[idx].particle = conf_to_particle(it.value, inherited);
|
||||
}
|
||||
|
||||
struct particle *default_particle = def != NULL
|
||||
? conf_to_particle(def, inherited) : NULL;
|
||||
|
||||
return map_new(
|
||||
common, yml_value_as_string(tag), particle_map, yml_dict_length(values),
|
||||
default_particle);
|
||||
return map_new(common, particle_map, yml_dict_length(conditions), default_particle);
|
||||
}
|
||||
|
||||
static bool
|
||||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"tag", true, &conf_verify_string},
|
||||
{"values", true, &verify_map_values},
|
||||
{"conditions", true, &verify_map_conditions},
|
||||
{"default", false, &conf_verify_particle},
|
||||
PARTICLE_COMMON_ATTRS,
|
||||
};
|
||||
|
|
|
@ -304,6 +304,7 @@ from_conf(const struct yml_node *node, struct particle *common)
|
|||
|
||||
struct conf_inherit inherited = {
|
||||
.font = common->font,
|
||||
.font_shaping = common->font_shaping,
|
||||
.foreground = common->foreground,
|
||||
};
|
||||
|
||||
|
@ -323,7 +324,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"tag", true, &conf_verify_string},
|
||||
{"length", true, &conf_verify_int},
|
||||
{"length", true, &conf_verify_unsigned},
|
||||
/* TODO: make these optional? Default to empty */
|
||||
{"start", true, &conf_verify_particle},
|
||||
{"end", true, &conf_verify_particle},
|
||||
|
|
|
@ -209,7 +209,7 @@ from_conf(const struct yml_node *node, struct particle *common)
|
|||
yml_list_next(&it), idx++)
|
||||
{
|
||||
parts[idx] = conf_to_particle(
|
||||
it.node, (struct conf_inherit){common->font, common->foreground});
|
||||
it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground});
|
||||
}
|
||||
|
||||
long min_v = min != NULL ? yml_value_as_int(min) : 0;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#define LOG_MODULE "string"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "../log.h"
|
||||
#include "../char32.h"
|
||||
#include "../config.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../particle.h"
|
||||
|
@ -87,7 +88,7 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int
|
|||
* its descent. This way, the part of the font *above* the
|
||||
* baseline is centered.
|
||||
*
|
||||
* "EEEE" will typically be dead center, with the middle of each character being in the bar's center.
|
||||
* "EEEE" will typically be dead center, with the middle of each character being in the bar's center.
|
||||
* "eee" will be slightly below the center.
|
||||
* "jjj" will be even further below the center.
|
||||
*
|
||||
|
@ -149,7 +150,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
|||
struct eprivate *e = calloc(1, sizeof(*e));
|
||||
struct fcft_font *font = particle->font;
|
||||
|
||||
wchar_t *wtext = NULL;
|
||||
char32_t *wtext = NULL;
|
||||
char *text = tags_expand_template(p->text, tags);
|
||||
|
||||
e->glyphs = e->allocated_glyphs = NULL;
|
||||
|
@ -173,17 +174,13 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
|||
}
|
||||
}
|
||||
|
||||
/* Not in cache - we need to rasterize it. First, convert to wchar */
|
||||
size_t chars = mbstowcs(NULL, text, 0);
|
||||
if (chars == (size_t)-1)
|
||||
goto done;
|
||||
|
||||
wtext = malloc((chars + 1) * sizeof(wtext[0]));
|
||||
mbstowcs(wtext, text, chars + 1);
|
||||
/* Not in cache - we need to rasterize it. First, convert to char32_t */
|
||||
wtext = ambstoc32(text);
|
||||
size_t chars = c32len(wtext);
|
||||
|
||||
/* Truncate, if necessary */
|
||||
if (p->max_len > 0) {
|
||||
const size_t len = wcslen(wtext);
|
||||
const size_t len = c32len(wtext);
|
||||
if (len > p->max_len) {
|
||||
|
||||
size_t end = p->max_len;
|
||||
|
@ -193,11 +190,11 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
|||
}
|
||||
|
||||
if (p->max_len > 1) {
|
||||
wtext[end] = L'…';
|
||||
wtext[end + 1] = L'\0';
|
||||
wtext[end] = U'…';
|
||||
wtext[end + 1] = U'\0';
|
||||
chars = end + 1;
|
||||
} else {
|
||||
wtext[end] = L'\0';
|
||||
wtext[end] = U'\0';
|
||||
chars = 0;
|
||||
}
|
||||
}
|
||||
|
@ -205,8 +202,10 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
|||
|
||||
e->kern_x = calloc(chars, sizeof(e->kern_x[0]));
|
||||
|
||||
if (fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING) {
|
||||
struct fcft_text_run *run = fcft_text_run_rasterize(
|
||||
if (particle->font_shaping == FONT_SHAPE_FULL &&
|
||||
fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING)
|
||||
{
|
||||
struct fcft_text_run *run = fcft_rasterize_text_run_utf32(
|
||||
font, chars, wtext, FCFT_SUBPIXEL_NONE);
|
||||
|
||||
if (run != NULL) {
|
||||
|
@ -250,7 +249,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
|||
|
||||
/* Convert text to glyph masks/images. */
|
||||
for (size_t i = 0; i < chars; i++) {
|
||||
const struct fcft_glyph *glyph = fcft_glyph_rasterize(
|
||||
const struct fcft_glyph *glyph = fcft_rasterize_char_utf32(
|
||||
font, wtext[i], FCFT_SUBPIXEL_NONE);
|
||||
|
||||
if (glyph == NULL)
|
||||
|
@ -323,7 +322,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node)
|
|||
{
|
||||
static const struct attr_info attrs[] = {
|
||||
{"text", true, &conf_verify_string},
|
||||
{"max", false, &conf_verify_int},
|
||||
{"max", false, &conf_verify_unsigned},
|
||||
PARTICLE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
|
|
8
plugin.c
8
plugin.c
|
@ -49,6 +49,8 @@ EXTERN_MODULE(sway_xkb);
|
|||
EXTERN_MODULE(script);
|
||||
EXTERN_MODULE(xkb);
|
||||
EXTERN_MODULE(xwindow);
|
||||
EXTERN_MODULE(cpu);
|
||||
EXTERN_MODULE(mem);
|
||||
|
||||
EXTERN_PARTICLE(empty);
|
||||
EXTERN_PARTICLE(list);
|
||||
|
@ -58,8 +60,10 @@ EXTERN_PARTICLE(ramp);
|
|||
EXTERN_PARTICLE(string);
|
||||
|
||||
EXTERN_DECORATION(background);
|
||||
EXTERN_DECORATION(border);
|
||||
EXTERN_DECORATION(stack);
|
||||
EXTERN_DECORATION(underline);
|
||||
EXTERN_DECORATION(overline);
|
||||
|
||||
#undef EXTERN_DECORATION
|
||||
#undef EXTERN_PARTICLE
|
||||
|
@ -135,6 +139,8 @@ init(void)
|
|||
#if defined(HAVE_PLUGIN_xwindow)
|
||||
REGISTER_CORE_MODULE(xwindow, xwindow);
|
||||
#endif
|
||||
REGISTER_CORE_MODULE(mem, mem);
|
||||
REGISTER_CORE_MODULE(cpu, cpu);
|
||||
|
||||
REGISTER_CORE_PARTICLE(empty, empty);
|
||||
REGISTER_CORE_PARTICLE(list, list);
|
||||
|
@ -144,8 +150,10 @@ init(void)
|
|||
REGISTER_CORE_PARTICLE(string, string);
|
||||
|
||||
REGISTER_CORE_DECORATION(background, background);
|
||||
REGISTER_CORE_DECORATION(border, border);
|
||||
REGISTER_CORE_DECORATION(stack, stack);
|
||||
REGISTER_CORE_DECORATION(underline, underline);
|
||||
REGISTER_CORE_DECORATION(overline, overline);
|
||||
|
||||
#undef REGISTER_CORE_DECORATION
|
||||
#undef REGISTER_CORE_PARTICLE
|
||||
|
|
40
tag.c
40
tag.c
|
@ -32,6 +32,30 @@ tag_name(const struct tag *tag)
|
|||
return priv->name;
|
||||
}
|
||||
|
||||
static enum tag_type
|
||||
bool_type(const struct tag *tag)
|
||||
{
|
||||
return TAG_TYPE_BOOL;
|
||||
}
|
||||
|
||||
static enum tag_type
|
||||
int_type(const struct tag *tag)
|
||||
{
|
||||
return TAG_TYPE_INT;
|
||||
}
|
||||
|
||||
static enum tag_type
|
||||
float_type(const struct tag *tag)
|
||||
{
|
||||
return TAG_TYPE_FLOAT;
|
||||
}
|
||||
|
||||
static enum tag_type
|
||||
string_type(const struct tag *tag)
|
||||
{
|
||||
return TAG_TYPE_STRING;
|
||||
}
|
||||
|
||||
static long
|
||||
unimpl_min_max(const struct tag *tag)
|
||||
{
|
||||
|
@ -264,6 +288,7 @@ tag_new_int_realtime(struct module *owner, const char *name, long value,
|
|||
tag->owner = owner;
|
||||
tag->destroy = &destroy_int_and_float;
|
||||
tag->name = &tag_name;
|
||||
tag->type = &int_type;
|
||||
tag->min = &int_min;
|
||||
tag->max = &int_max;
|
||||
tag->realtime = &int_realtime;
|
||||
|
@ -287,6 +312,7 @@ tag_new_bool(struct module *owner, const char *name, bool value)
|
|||
tag->owner = owner;
|
||||
tag->destroy = &destroy_int_and_float;
|
||||
tag->name = &tag_name;
|
||||
tag->type = &bool_type;
|
||||
tag->min = &unimpl_min_max;
|
||||
tag->max = &unimpl_min_max;
|
||||
tag->realtime = &no_realtime;
|
||||
|
@ -310,6 +336,7 @@ tag_new_float(struct module *owner, const char *name, double value)
|
|||
tag->owner = owner;
|
||||
tag->destroy = &destroy_int_and_float;
|
||||
tag->name = &tag_name;
|
||||
tag->type = &float_type;
|
||||
tag->min = &unimpl_min_max;
|
||||
tag->max = &unimpl_min_max;
|
||||
tag->realtime = &no_realtime;
|
||||
|
@ -333,6 +360,7 @@ tag_new_string(struct module *owner, const char *name, const char *value)
|
|||
tag->owner = owner;
|
||||
tag->destroy = &destroy_string;
|
||||
tag->name = &tag_name;
|
||||
tag->type = &string_type;
|
||||
tag->min = &unimpl_min_max;
|
||||
tag->max = &unimpl_min_max;
|
||||
tag->realtime = &no_realtime;
|
||||
|
@ -545,12 +573,12 @@ tags_expand_template(const char *template, const struct tag_set *tags)
|
|||
case FMT_MIBYTE:
|
||||
case FMT_GIBYTE: {
|
||||
const long divider =
|
||||
format == FMT_KBYTE ? 1024 :
|
||||
format == FMT_MBYTE ? 1024 * 1024 :
|
||||
format == FMT_GBYTE ? 1024 * 1024 * 1024 :
|
||||
format == FMT_KIBYTE ? 1000 :
|
||||
format == FMT_MIBYTE ? 1000 * 1000 :
|
||||
format == FMT_GIBYTE ? 1000 * 1000 * 1000 :
|
||||
format == FMT_KBYTE ? 1000 :
|
||||
format == FMT_MBYTE ? 1000 * 1000 :
|
||||
format == FMT_GBYTE ? 1000 * 1000 * 1000 :
|
||||
format == FMT_KIBYTE ? 1024 :
|
||||
format == FMT_MIBYTE ? 1024 * 1024 :
|
||||
format == FMT_GIBYTE ? 1024 * 1024 * 1024 :
|
||||
1;
|
||||
|
||||
char str[24];
|
||||
|
|
8
tag.h
8
tag.h
|
@ -3,6 +3,13 @@
|
|||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum tag_type {
|
||||
TAG_TYPE_BOOL,
|
||||
TAG_TYPE_INT,
|
||||
TAG_TYPE_FLOAT,
|
||||
TAG_TYPE_STRING,
|
||||
};
|
||||
|
||||
enum tag_realtime_unit {
|
||||
TAG_REALTIME_NONE,
|
||||
TAG_REALTIME_SECS,
|
||||
|
@ -17,6 +24,7 @@ struct tag {
|
|||
|
||||
void (*destroy)(struct tag *tag);
|
||||
const char *(*name)(const struct tag *tag);
|
||||
enum tag_type (*type)(const struct tag *tag);
|
||||
const char *(*as_string)(const struct tag *tag);
|
||||
long (*as_int)(const struct tag *tag);
|
||||
bool (*as_bool)(const struct tag *tag);
|
||||
|
|
|
@ -62,10 +62,9 @@ bar:
|
|||
- clock:
|
||||
content:
|
||||
map:
|
||||
tag: date
|
||||
default: {string: {text: default value}}
|
||||
values:
|
||||
1234: {string: {text: specific value}}
|
||||
conditions:
|
||||
date == 1234: {string: {text: specific value}}
|
||||
- clock:
|
||||
content:
|
||||
progress-bar:
|
||||
|
|
Loading…
Add table
Reference in a new issue