Merge branch 'master' into master

This commit is contained in:
Mohammad Zeinali 2022-05-02 22:44:56 +02:00 committed by mzeinali
commit 47bc23c8f1
69 changed files with 2623 additions and 526 deletions

View file

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

View file

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

View file

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

View file

@ -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: dont 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: dont 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 tags 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

View file

@ -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=()

View file

@ -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() {

View file

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

View file

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

View file

@ -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 = []

View file

@ -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,
};

View file

@ -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
View 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
View 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);

View file

@ -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},
};

View file

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

View file

@ -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,
};

View file

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

View file

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

View file

@ -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,
};

View file

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

View file

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

View file

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

View 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)

View file

@ -67,10 +67,9 @@ bar:
- foreign-toplevel:
content:
map:
tag: activated
values:
false: {empty: {}}
true:
conditions:
~activated: {empty: {}}
activated:
- string: {text: "{app-id}: {title}"}
```

View file

@ -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}"}}
```

View 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)

View file

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

View file

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

View file

@ -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}"}}
```

View file

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

View file

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

View file

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

View file

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

View file

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

View 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}}}}

View file

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

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

4
log.c
View file

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

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

View file

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

View file

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

View file

@ -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,
};

View file

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

View file

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

View file

@ -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': [[], []],

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,
};

View file

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

View file

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

View file

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

View file

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

View file

@ -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,
};

View file

@ -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,
};

View file

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

View file

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

View file

@ -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,
};

View file

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

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

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

View file

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