From 4f3aa8c6b07381ea459a5d6c12dcf2774aa002eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Oct 2021 18:05:20 +0200 Subject: [PATCH 01/84] =?UTF-8?q?bar:=20do=20a=20synchronous=20=E2=80=9Cre?= =?UTF-8?q?fresh=E2=80=9D=20*before*=20starting=20the=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures the surface has been mapped, and our “current” output is known. This fixes an issue where modules depending on the current output being known failed to update correctly during startup. --- bar/bar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bar/bar.c b/bar/bar.c index 8b19a43..4f1895f 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -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"); From fee0b91174e4a9117b1955a51bbaaf785348bcd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Oct 2021 18:06:19 +0200 Subject: [PATCH 02/84] =?UTF-8?q?bar/wayland:=20remove=20unused=20member?= =?UTF-8?q?=20=E2=80=98bar=5Fexpose=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bar/wayland.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/bar/wayland.c b/bar/wayland.c index 696f598..4f69135 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -116,7 +116,6 @@ struct wayland_backend { 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); }; @@ -1111,7 +1110,6 @@ loop(struct bar *_bar, 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) From 939b81a4ea72896a1b9f4f21653bbdc26be64e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Oct 2021 18:06:43 +0200 Subject: [PATCH 03/84] bar/wayland: create comm pipe with CLOEXEC | NONBLOCK --- bar/wayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bar/wayland.c b/bar/wayland.c index 4f69135..02e9ad6 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -1029,7 +1029,7 @@ setup(struct bar *_bar) 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; } From bd44e82ecaefbd2056eeb58bba48209b94b28c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Oct 2021 18:07:14 +0200 Subject: [PATCH 04/84] =?UTF-8?q?bar/wayland:=20coalesce=20=E2=80=9Crefres?= =?UTF-8?q?h=E2=80=9D=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces the number of repaints primarily during startup, but also when e.g. switching workspace. --- bar/wayland.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/bar/wayland.c b/bar/wayland.c index 02e9ad6..90d1103 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -1139,16 +1140,30 @@ loop(struct bar *_bar, } 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"); + break; + } + + 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) { From cad9dd8efdc67de7c35e0109f41726f8d38d2a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Oct 2021 16:03:21 +0200 Subject: [PATCH 05/84] ci: also build release branches --- .woodpecker.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 875213a..4ea84f1 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -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: From 7d715d81a8e89bfd271c710cd89bb0181dc54d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Oct 2021 17:24:15 +0200 Subject: [PATCH 06/84] =?UTF-8?q?example:=20conf:=20use=20foreign-toplevel?= =?UTF-8?q?=20instead=20of=20i3=E2=80=99s=20=E2=80=98current=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/configurations/laptop.conf | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index 0c31b20..65143cc 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -105,23 +105,17 @@ bar: unfocused: {string: {<<: *music}} invisible: {string: {<<: [*music, *invisible]}} urgent: {string: {<<: [*music, *urgent]}} - current: - map: - left-margin: 7 - tag: application - values: - "": - - map: {<<: *i3_mode} - - string: {text: "{title}"} - default: - list: - spacing: 0 - items: - - map: {<<: *i3_mode} - - string: {text: "{application}", max: 10, foreground: ffa0a0ff} - - string: {text: ": "} - - string: {text: "{title}", max: 35} + - foreign-toplevel: + content: + map: + tag: activated + values: + false: {empty: {}} + true: + - string: {text: "{app-id}", foreground: ffa0a0ff} + - string: {text: ": "} + - string: {text: "{title}"} center: - mpd: host: /run/mpd/socket From 4e96dbd7f7fefed3a7a79e7cabc0c3c08bd5623a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Oct 2021 17:35:15 +0200 Subject: [PATCH 07/84] =?UTF-8?q?network:=20don=E2=80=99t=20send=20nl80211?= =?UTF-8?q?=20request=20if=20we=20don=E2=80=99t=20have=20a=20family-id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an issue where we sometimes failed to retrieve the SSID; we sent one initial request before the family ID had been set. Then, we received the family ID and tried to send another request. However, if the first request was still in progress, the second request was never made. Since the first request lacked the family ID, that response didn’t contain anything useful. --- modules/network.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/network.c b/modules/network.c index bf32c57..117f687 100644 --- a/modules/network.c +++ b/modules/network.c @@ -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 { From 9ffd305b594cf4767da3af8d33da9e02efb67b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Oct 2021 17:47:54 +0200 Subject: [PATCH 08/84] network: must re-send station request when we receive family ID The get station request also fails if the family ID is invalid. --- modules/network.c | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/network.c b/modules/network.c index 117f687..06ada9b 100644 --- a/modules/network.c +++ b/modules/network.c @@ -695,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; } From 31f160141e0250e3b4c090e6a33d5a53cd85c5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Oct 2021 17:50:10 +0200 Subject: [PATCH 09/84] changelog: network: failure to retrieve wireless attributes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6990b6b..84f80e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ * 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). +* network: failure to retrieve wireless attributes (SSID, RX/TX + bitrate, signal strength etc). ### Security From f0782d5124f9f6c72a5e07c2dfdb6d3d98cc9e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Oct 2021 18:08:39 +0200 Subject: [PATCH 10/84] =?UTF-8?q?deco:=20new=20decoration,=20=E2=80=98bord?= =?UTF-8?q?er=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kind of like “underline”, but draws a border around the entire particle. --- CHANGELOG.md | 1 + decorations/border.c | 93 +++++++++++++++++++++++++++++++++++++++++ decorations/meson.build | 2 +- plugin.c | 2 + 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 decorations/border.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f80e3..d102c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Added * ramp: can now have custom min and max values (https://codeberg.org/dnkl/yambar/issues/103). +* border: new decoration. ### Changed diff --git a/decorations/border.c b/decorations/border.c new file mode 100644 index 0000000..ac50c33 --- /dev/null +++ b/decorations/border.c @@ -0,0 +1,93 @@ +#include + +#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_int}, + 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 diff --git a/decorations/meson.build b/decorations/meson.build index 708267e..e8b289c 100644 --- a/decorations/meson.build +++ b/decorations/meson.build @@ -1,7 +1,7 @@ deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft]) decorations = [] -foreach deco : ['background', 'stack', 'underline'] +foreach deco : ['background', 'border', 'stack', 'underline'] if plugs_as_libs shared_module('@0@'.format(deco), '@0@.c'.format(deco), dependencies: deco_sdk, diff --git a/plugin.c b/plugin.c index 00b0061..5f31b2e 100644 --- a/plugin.c +++ b/plugin.c @@ -58,6 +58,7 @@ EXTERN_PARTICLE(ramp); EXTERN_PARTICLE(string); EXTERN_DECORATION(background); +EXTERN_DECORATION(border); EXTERN_DECORATION(stack); EXTERN_DECORATION(underline); @@ -144,6 +145,7 @@ 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); From 134141b7c58844494da2ae488e394f7562363062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Oct 2021 12:17:00 +0200 Subject: [PATCH 11/84] =?UTF-8?q?doc:=20decorations:=20document=20the=20ne?= =?UTF-8?q?w=20=E2=80=98border=E2=80=99=20decoration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/yambar-decorations.5.scd | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/doc/yambar-decorations.5.scd b/doc/yambar-decorations.5.scd index 48dcc87..8c972f2 100644 --- a/doc/yambar-decorations.5.scd +++ b/doc/yambar-decorations.5.scd @@ -70,6 +70,39 @@ content: 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. From 66ec7a85a19b018b95f310a17d1baf5c864ce3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Oct 2021 18:34:43 +0200 Subject: [PATCH 12/84] =?UTF-8?q?example:=20conf:=20foreign-toplevel:=20me?= =?UTF-8?q?rge=20=E2=80=98:=20=E2=80=99=20and=20=E2=80=98{title}=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/configurations/laptop.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index 65143cc..19d0bb4 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -114,8 +114,7 @@ bar: false: {empty: {}} true: - string: {text: "{app-id}", foreground: ffa0a0ff} - - string: {text: ": "} - - string: {text: "{title}"} + - string: {text: ": {title}"} center: - mpd: host: /run/mpd/socket From 832411250433fe73acdec0b09d9654c8725b729a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 30 Oct 2021 18:45:43 +0200 Subject: [PATCH 13/84] =?UTF-8?q?example:=20battery:=20map=20=E2=80=98unkn?= =?UTF-8?q?own=E2=80=99=20to=20=E2=80=98discharging=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to #123 --- examples/configurations/laptop.conf | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index 19d0bb4..3a6c439 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -248,11 +248,10 @@ bar: - battery: name: BAT0 poll-interval: 30 - content: - map: - tag: state - values: - discharging: + anchors: + discharging: &discharging + list: + items: - ramp: tag: capacity items: @@ -267,6 +266,14 @@ bar: - string: {text: , font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}% {estimate}"} + content: + map: + tag: state + values: + unknown: + <<: *discharging + discharging: + <<: *discharging charging: - string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}% {estimate}"} From 58038a42361b60ee9c9d72612c1ab96c5a9baf4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 31 Oct 2021 21:07:09 +0100 Subject: [PATCH 14/84] doc: battery: some batteries enter "unknown" under normal operation --- doc/yambar-modules-battery.5.scd | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/yambar-modules-battery.5.scd b/doc/yambar-modules-battery.5.scd index 42b5e00..3d55ed9 100644 --- a/doc/yambar-modules-battery.5.scd +++ b/doc/yambar-modules-battery.5.scd @@ -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* From d40220e51196e517c97602ec7a236236c4701481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:06:10 +0100 Subject: [PATCH 15/84] bar/wayland: handle failure to set initial size in setup() --- bar/wayland.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bar/wayland.c b/bar/wayland.c index 90d1103..a5a6731 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -1025,7 +1025,8 @@ setup(struct bar *_bar) ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | top_or_bottom); - update_size(backend); + if (!update_size(backend)) + return false; assert(backend->monitor == NULL || backend->width / backend->monitor->scale <= backend->monitor->width_px); From 11bb45aa878b4cc7a5ddc5ed0640e27403fddc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:15:52 +0100 Subject: [PATCH 16/84] doc: script: add missing column in options table --- doc/yambar-modules-script.5.scd | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd index 7ba46d8..c1f31c6 100644 --- a/doc/yambar-modules-script.5.scd +++ b/doc/yambar-modules-script.5.scd @@ -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. From 72056c50cfec93e1ac41ded7ec4f177f7af5a0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:16:15 +0100 Subject: [PATCH 17/84] config-verify: add conf_verify_unsigned() Like conf_verify_int(), but also requires the integer to be >= 0 --- config-verify.c | 11 +++++++++++ config-verify.h | 1 + 2 files changed, 12 insertions(+) diff --git a/config-verify.c b/config-verify.c index 63efef3..eef8059 100644 --- a/config-verify.c +++ b/config-verify.c @@ -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) { diff --git a/config-verify.h b/config-verify.h index 5afe3f6..8e5ab29 100644 --- a/config-verify.h +++ b/config-verify.h @@ -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, From 23f12a65b2ef168d58ddf97a413220b9f4a811e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:16:37 +0100 Subject: [PATCH 18/84] conf: bar/border: verify: all integer options are supposed to be unsigned --- config-verify.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/config-verify.c b/config-verify.c index eef8059..b92fb71 100644 --- a/config-verify.c +++ b/config-verify.c @@ -392,17 +392,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}, }; @@ -433,20 +433,20 @@ 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}, @@ -456,7 +456,7 @@ conf_verify_bar(const struct yml_node *bar) {"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}, }; From b6931c6ed0bd99f486201ea6d8b1504d6966e317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:17:12 +0100 Subject: [PATCH 19/84] decos: verify: all integer options are supposed to be unsigned --- decorations/border.c | 2 +- decorations/underline.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/decorations/border.c b/decorations/border.c index ac50c33..5868e88 100644 --- a/decorations/border.c +++ b/decorations/border.c @@ -76,7 +76,7 @@ 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_int}, + {"size", false, &conf_verify_unsigned}, DECORATION_COMMON_ATTRS, }; diff --git a/decorations/underline.c b/decorations/underline.c index a700bec..5b8bbd3 100644 --- a/decorations/underline.c +++ b/decorations/underline.c @@ -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, }; From f166bbbf5449e2c394c55659fc46482a245d4f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:17:29 +0100 Subject: [PATCH 20/84] modules: verify: use conf_verify_unsigned() for options that should be >= 0 --- modules/battery.c | 2 +- modules/i3.c | 6 +++--- modules/mpd.c | 2 +- modules/network.c | 2 +- modules/removables.c | 6 +++--- modules/script.c | 2 +- modules/sway-xkb.c | 6 +++--- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/battery.c b/modules/battery.c index 4f17de4..e6526f5 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -496,7 +496,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, }; diff --git a/modules/i3.c b/modules/i3.c index 5e5c80a..0ed6927 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -878,9 +878,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}, {"sort", false, &verify_sort}, {"persistent", false, &verify_persistent}, {"content", true, &verify_content}, diff --git a/modules/mpd.c b/modules/mpd.c index bebd401..274fdc8 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -616,7 +616,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"host", true, &conf_verify_string}, - {"port", false, &conf_verify_int}, + {"port", false, &conf_verify_unsigned}, MODULE_COMMON_ATTRS, }; diff --git a/modules/network.c b/modules/network.c index 06ada9b..3bff716 100644 --- a/modules/network.c +++ b/modules/network.c @@ -1257,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, }; diff --git a/modules/removables.c b/modules/removables.c index 04d2695..3a54379 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -652,9 +652,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, }; diff --git a/modules/script.c b/modules/script.c index 2ddd722..254aac4 100644 --- a/modules/script.c +++ b/modules/script.c @@ -709,7 +709,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, }; diff --git a/modules/sway-xkb.c b/modules/sway-xkb.c index f4e7577..9841feb 100644 --- a/modules/sway-xkb.c +++ b/modules/sway-xkb.c @@ -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, }; From 4f0d27bc7efd1ed83a82db36a5b15b542764aba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:17:52 +0100 Subject: [PATCH 21/84] particles: verify: use conf_verify_unsigned() for options that should be >= 0 --- particle.h | 16 ++++++++-------- particles/list.c | 6 +++--- particles/progress-bar.c | 2 +- particles/string.c | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/particle.h b/particle.h index 4ca520f..7c5685e 100644 --- a/particle.h +++ b/particle.h @@ -76,12 +76,12 @@ 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}, \ + {"foreground", false, &conf_verify_color}, \ + {"deco", false, &conf_verify_decoration}, \ {NULL, false, NULL} diff --git a/particles/list.c b/particles/list.c index 19ff15b..e5cf883 100644 --- a/particles/list.c +++ b/particles/list.c @@ -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, }; diff --git a/particles/progress-bar.c b/particles/progress-bar.c index ebc51c3..462d536 100644 --- a/particles/progress-bar.c +++ b/particles/progress-bar.c @@ -323,7 +323,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}, diff --git a/particles/string.c b/particles/string.c index c475d5b..f6083f6 100644 --- a/particles/string.c +++ b/particles/string.c @@ -323,7 +323,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, }; From 9d94cec549c95b5c3b36a6e28247b22ec66c6934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 15 Nov 2021 18:21:19 +0100 Subject: [PATCH 22/84] changelog: integer options incorrectly allowed < 0 values --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d102c7d..74021a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ “refreshed” it (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 + (https://codeberg.org/dnkl/yambar/issues/129). ### Security From b562f1310b541cded6940226b97d97354c57ce81 Mon Sep 17 00:00:00 2001 From: Catterwocky Date: Sat, 4 Dec 2021 17:49:39 +0100 Subject: [PATCH 23/84] Fix yaml indentation in docs It is unfortunate that the first example given by the manpage is not working. --- doc/yambar.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/yambar.5.scd b/doc/yambar.5.scd index 47041fb..d3a585f 100644 --- a/doc/yambar.5.scd +++ b/doc/yambar.5.scd @@ -161,8 +161,8 @@ bar: right: - clock: - content: - - string: {text: "{time}"} + content: + - string: {text: "{time}"} ``` # FILES From 39a549345067163a3e8fb357de9a38a7e01a7626 Mon Sep 17 00:00:00 2001 From: natemaia Date: Thu, 9 Dec 2021 02:07:24 +0100 Subject: [PATCH 24/84] Fix: X11 struts https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html#idm45643490065584 --- bar/xcb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bar/xcb.c b/bar/xcb.c index 4b0e7c1..d8c5f9c 100644 --- a/bar/xcb.c +++ b/bar/xcb.c @@ -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; From 6612a9af9f7a9aece96629e6032a5600a1704777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:23:59 +0100 Subject: [PATCH 25/84] module/backlight: handle poll() failures --- modules/backlight.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/backlight.c b/modules/backlight.c index 292b067..af58280 100644 --- a/modules/backlight.c +++ b/modules/backlight.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -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 * From f9229734501aca189c946479031c2b5e74fb9993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:25:37 +0100 Subject: [PATCH 26/84] module/battery: handle poll() failures --- modules/battery.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/battery.c b/modules/battery.c index e6526f5..0833310 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -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; From 82ef48f666a55c1f09be92431481951bb36b19bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:27:53 +0100 Subject: [PATCH 27/84] module/clock: remove unused include --- modules/clock.c | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/clock.c b/modules/clock.c index 2956a40..eddc7be 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -67,7 +67,6 @@ content(struct module *mod) return exposable; } -#include static int run(struct module *mod) { From cdd0b5b4f06be3434b13205aa26be807282ab578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:28:05 +0100 Subject: [PATCH 28/84] module/clock: fold long line --- modules/clock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/clock.c b/modules/clock.c index eddc7be..49b832e 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -131,7 +131,8 @@ run(struct module *mod) } 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; From 8a11a3fbe5a8094857d8df001ceaff9906847870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:28:14 +0100 Subject: [PATCH 29/84] module/clock: handle poll() failures --- modules/clock.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/clock.c b/modules/clock.c index 49b832e..15392aa 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -74,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); @@ -119,15 +122,23 @@ 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 * From 5d09e59f111c6a053697852e218cefea280cf58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:29:26 +0100 Subject: [PATCH 30/84] module/mpd: handle poll() failures --- modules/mpd.c | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/modules/mpd.c b/modules/mpd.c index 274fdc8..b2e9e6e 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -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 { From ffa86d84a5546c5a20f463b8eb3276c88415f885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:40:12 +0100 Subject: [PATCH 31/84] module/removables: handle poll() failures --- modules/removables.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/removables.c b/modules/removables.c index 3a54379..736317f 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -547,16 +548,26 @@ run(struct module *mod) * mount/unmount operations */ int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY); + int ret = 1; + while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.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 +598,7 @@ run(struct module *mod) udev_monitor_unref(dev_mon); udev_unref(udev); - return 0; + return ret; } static struct module * From 96c75b7f73697c1f2cd84f24fbec8aa240ae0570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:42:53 +0100 Subject: [PATCH 32/84] module/xkb: handle poll() failures --- modules/xkb.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/xkb.c b/modules/xkb.c index 5c2c1f9..965664d 100644 --- a/modules/xkb.c +++ b/modules/xkb.c @@ -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; From 9cff50a39ef757074ebec258aad2b20e27bc61af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Dec 2021 11:42:59 +0100 Subject: [PATCH 33/84] module/xwindow: handle poll() failures --- modules/xwindow.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/xwindow.c b/modules/xwindow.c index 3925f4f..baf1239 100644 --- a/modules/xwindow.c +++ b/modules/xwindow.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -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 * From 3e133d86186b0dcdcebca617591fe8256fe62044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 19 Dec 2021 17:48:58 +0100 Subject: [PATCH 34/84] =?UTF-8?q?module/i3:=20add=20=E2=80=98empty?= =?UTF-8?q?=E2=80=99=20boolean=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set to true for empty (no windows) workspaces. Mostly useful with persistent workspaces, to be able to differentiate between invisible non-empty workspaces and actually empty workspaces (the latter not being possible with non-persistent workspaces). --- modules/i3.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/modules/i3.c b/modules/i3.c index 0ed6927..56dec4d 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -37,6 +37,7 @@ struct workspace { bool visible; bool focused; bool urgent; + bool empty; struct { unsigned id; @@ -85,14 +86,17 @@ static bool workspace_from_json(const struct json_object *json, struct workspace *ws) { /* Always present */ - struct json_object *name, *output; + struct json_object *name, *output, *focus; if (!json_object_object_get_ex(json, "name", &name) || - !json_object_object_get_ex(json, "output", &output)) + !json_object_object_get_ex(json, "output", &output) || + !json_object_object_get_ex(json, "focus", &focus)) { - LOG_ERR("workspace reply/event without 'name' and/or 'output' property"); + LOG_ERR("workspace reply/event without 'name' and/or 'output', " + "and/or 'focus' properties"); return false; } + /* Optional */ struct json_object *visible = NULL, *focused = NULL, *urgent = NULL; json_object_object_get_ex(json, "visible", &visible); @@ -101,6 +105,9 @@ 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 = json_object_array_length(focus); + const bool is_empty = node_count == 0; + *ws = (struct workspace) { .name = strdup(name_as_string), .name_as_int = workspace_name_as_int(name_as_string), @@ -109,6 +116,7 @@ workspace_from_json(const struct json_object *json, struct workspace *ws) .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 +361,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 +434,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 +464,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) || @@ -602,6 +619,7 @@ run(struct module *mod) .name = strdup(name_as_string), .name_as_int = workspace_name_as_int(name_as_string), .persistent = true, + .empty = true, }; workspace_add(m, ws); } @@ -695,12 +713,25 @@ 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); + struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", ws->name), tag_new_bool(mod, "visible", ws->visible), tag_new_bool(mod, "focused", ws->focused), tag_new_bool(mod, "urgent", ws->urgent), + tag_new_bool(mod, "empty", ws->empty), tag_new_string(mod, "state", state), tag_new_string(mod, "application", ws->window.application), @@ -708,7 +739,7 @@ content(struct module *mod) tag_new_string(mod, "mode", m->mode), }, - .count = 8, + .count = 9, }; if (ws->focused) { From 8475ca160358c5db2bb3c8b80b15df957f71b07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 19 Dec 2021 17:51:01 +0100 Subject: [PATCH 35/84] =?UTF-8?q?doc:=20i3:=20document=20the=20new=20?= =?UTF-8?q?=E2=80=98empty=E2=80=99=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/yambar-modules-i3.5.scd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd index 8bf4a75..2cae2f8 100644 --- a/doc/yambar-modules-i3.5.scd +++ b/doc/yambar-modules-i3.5.scd @@ -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 | state : string : One of *urgent*, *focused*, *unfocused* or *invisible* (note: From 26ea13793894202145da42d53c8ad045f226bd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 19 Dec 2021 17:51:12 +0100 Subject: [PATCH 36/84] =?UTF-8?q?changelog:=20i3/sway:=20=E2=80=98empty?= =?UTF-8?q?=E2=80=99=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74021a7..fba854f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,12 @@ ## Unreleased ### Added + * ramp: can now have custom min and max values (https://codeberg.org/dnkl/yambar/issues/103). * border: new decoration. +* i3/sway: new boolean tag: `empty` + (https://codeberg.org/dnkl/yambar/issues/139). ### Changed From f7206ef08d59f1047a3a7c85bd9b6b12f3d0ac34 Mon Sep 17 00:00:00 2001 From: horus645 Date: Sat, 18 Dec 2021 22:17:20 -0300 Subject: [PATCH 37/84] Added documentation for discriminated on-click events --- doc/yambar-particles.5.scd | 45 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index ab689a0..a633269 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -36,15 +36,56 @@ following attributes are supported by all particles: : 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: + : + on-click: command args +``` + +*on-click* as an associative array (handles other buttons): +``` +content: + : + 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 From d8d44b0f33547e25645d497edf3ee05a9f38040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 21 Dec 2021 13:41:46 +0100 Subject: [PATCH 38/84] meson: generate-version: use CURRENT_SOURCE_DIR instead of SOURCE_ROOT --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ed4d0d1..66f200c 100644 --- a/meson.build +++ b/meson.build @@ -107,7 +107,7 @@ 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', From 337ce7681f835bb3caf1a7027b7804e5ee9209d1 Mon Sep 17 00:00:00 2001 From: Alexandre Acebedo Date: Sat, 11 Dec 2021 18:18:21 +0100 Subject: [PATCH 39/84] modules: add mem module --- CHANGELOG.md | 1 + doc/meson.build | 1 + doc/yambar-modules-mem.5.scd | 51 ++++++++++ modules/mem.c | 192 +++++++++++++++++++++++++++++++++++ modules/meson.build | 1 + plugin.c | 2 + 6 files changed, 248 insertions(+) create mode 100644 doc/yambar-modules-mem.5.scd create mode 100644 modules/mem.c diff --git a/CHANGELOG.md b/CHANGELOG.md index fba854f..3947faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * border: new decoration. * i3/sway: new boolean tag: `empty` (https://codeberg.org/dnkl/yambar/issues/139). +* mem: a module handling system memory monitoring ### Changed diff --git a/doc/meson.build b/doc/meson.build index a1550c4..3bacdd8 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -13,6 +13,7 @@ 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-mem.5.scd', 'yambar-particles.5.scd', 'yambar-tags.5.scd'] parts = man_src.split('.') name = parts[-3] diff --git a/doc/yambar-modules-mem.5.scd b/doc/yambar-modules-mem.5.scd new file mode 100644 index 0000000..cec575c --- /dev/null +++ b/doc/yambar-modules-mem.5.scd @@ -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) diff --git a/modules/mem.c b/modules/mem.c new file mode 100644 index 0000000..d79861e --- /dev/null +++ b/modules/mem.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/modules/meson.build b/modules/meson.build index 6b64958..635ac03 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -17,6 +17,7 @@ mod_data = { 'backlight': [[], [m, udev]], 'battery': [[], [udev]], 'clock': [[], []], + 'mem': [[], []], 'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]], 'label': [[], []], 'network': [[], []], diff --git a/plugin.c b/plugin.c index 5f31b2e..becc152 100644 --- a/plugin.c +++ b/plugin.c @@ -49,6 +49,7 @@ EXTERN_MODULE(sway_xkb); EXTERN_MODULE(script); EXTERN_MODULE(xkb); EXTERN_MODULE(xwindow); +EXTERN_MODULE(mem); EXTERN_PARTICLE(empty); EXTERN_PARTICLE(list); @@ -136,6 +137,7 @@ init(void) #if defined(HAVE_PLUGIN_xwindow) REGISTER_CORE_MODULE(xwindow, xwindow); #endif + REGISTER_CORE_MODULE(mem, mem); REGISTER_CORE_PARTICLE(empty, empty); REGISTER_CORE_PARTICLE(list, list); From ae5c7e0fc37cb19eb63bc9990beddcc23fb09833 Mon Sep 17 00:00:00 2001 From: Alexandre Acebedo Date: Sat, 11 Dec 2021 18:20:04 +0100 Subject: [PATCH 40/84] modules: add cpu module --- CHANGELOG.md | 1 + doc/meson.build | 1 + doc/yambar-modules-cpu.5.scd | 42 +++++ modules/cpu.c | 294 +++++++++++++++++++++++++++++++++++ modules/meson.build | 1 + plugin.c | 2 + 6 files changed, 341 insertions(+) create mode 100644 doc/yambar-modules-cpu.5.scd create mode 100644 modules/cpu.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 3947faf..9a834b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * i3/sway: new boolean tag: `empty` (https://codeberg.org/dnkl/yambar/issues/139). * mem: a module handling system memory monitoring +* cpu: a module offering cpu usage monitoring ### Changed diff --git a/doc/meson.build b/doc/meson.build index 3bacdd8..0d7f873 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -13,6 +13,7 @@ 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('.') diff --git a/doc/yambar-modules-cpu.5.scd b/doc/yambar-modules-cpu.5.scd new file mode 100644 index 0000000..2a05fdf --- /dev/null +++ b/doc/yambar-modules-cpu.5.scd @@ -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) diff --git a/modules/cpu.c b/modules/cpu.c new file mode 100644 index 0000000..0967dfd --- /dev/null +++ b/modules/cpu.c @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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() +{ + uint32_t nb_cores = 0; + FILE *fp = NULL; + char *line = NULL; + size_t len = 0; + ssize_t read; + + fp = fopen("/proc/cpuinfo", "r"); + if (NULL == fp) { + LOG_ERRNO("unable to open /proc/cpuinfo"); + return 0; + } + while ((read = getline(&line, &len, fp)) != -1) { + if (strncmp(line, "siblings", sizeof("siblings") - 1) == 0) { + char *pos = (char *)memchr(line, ':', read); + if (pos == NULL) { + LOG_ERR("unable to parse siblings field to find the number of cores"); + return 0; + } + errno = 0; + nb_cores = strtoul(pos + 1, NULL, 10); + if (errno == ERANGE) { + LOG_ERR("number of cores is out of range"); + } + break; + } + } + fclose(fp); + free(line); + + return nb_cores; +} + +static bool +parse_proc_stat_line(const char *line, int32_t *core, 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) +{ + 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); + *core = -1; + return read == 10; + } else { + int read = sscanf(line, + "cpu%" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 + " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32, + core, 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) { + if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) { + if (!parse_proc_stat_line(line, &core, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, + &guest, &guestnice) + || core < -1 || core >= (int32_t)nb_cores) { + LOG_ERR("unable to parse /proc/stat line"); + goto exit; + } + + cpu_stats->prev_cores_idle[core + 1] = cpu_stats->cur_cores_idle[core + 1]; + cpu_stats->prev_cores_nidle[core + 1] = cpu_stats->cur_cores_nidle[core + 1]; + + cpu_stats->cur_cores_idle[core + 1] = idle + iowait; + cpu_stats->cur_cores_nidle[core + 1] = user + nice + system + irq + softirq + steal; + } + } +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 diff --git a/modules/meson.build b/modules/meson.build index 635ac03..192b094 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -17,6 +17,7 @@ mod_data = { 'backlight': [[], [m, udev]], 'battery': [[], [udev]], 'clock': [[], []], + 'cpu': [[], []], 'mem': [[], []], 'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json]], 'label': [[], []], diff --git a/plugin.c b/plugin.c index becc152..1faae0e 100644 --- a/plugin.c +++ b/plugin.c @@ -49,6 +49,7 @@ EXTERN_MODULE(sway_xkb); EXTERN_MODULE(script); EXTERN_MODULE(xkb); EXTERN_MODULE(xwindow); +EXTERN_MODULE(cpu); EXTERN_MODULE(mem); EXTERN_PARTICLE(empty); @@ -138,6 +139,7 @@ init(void) 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); From e83c4bd8c196a3713f36cac964316c7a830459d8 Mon Sep 17 00:00:00 2001 From: Alexandre Acebedo Date: Sun, 12 Dec 2021 18:13:52 +0100 Subject: [PATCH 41/84] misc: add format files for clang-format and editorconfig --- .clang-format | 17 +++++++++++++++++ .editorconfig | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .clang-format create mode 100644 .editorconfig diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..528a36b --- /dev/null +++ b/.clang-format @@ -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 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ef74858 --- /dev/null +++ b/.editorconfig @@ -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 From d9316a202deb1429380952c39310ac021e4a2b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 26 Dec 2021 12:17:49 +0100 Subject: [PATCH 42/84] module/removables: audio CD support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audio CDs are special, in that they don’t (usually) have any data partitions. They also don’t have a volume label. They just have tracks. Before this patch, we ignored all optical mediums that did *not* have a filesystem (that includes audio CDs). Now, instead of using the ID_FS_USAGE property to determine whether there’s media present in the CD-ROM or not, we use the ID_CDROM_MEDIA. This property is set to ‘1’ for both audio CDs and data CDs. Then, we read the ID_CDROM_MEDIA_TRACK_COUNT_AUDIO property to determine how many audio tracks there are. If the CD has a filesystem, we treat it as a data CD, and use the already existing add_partition() function to track it. If the CD does _not_ have a filesystem, but it _does_ have at least one audio track, we treat it as an audio CD and use the new add_audio_cd() function to track it. This function is almost identical to add_partition(), but instead of reading the ID_FS_LABEL property, it reads the ID_CDROM_MEDIA_TRACK_COUNT_AUDIO property and synthesizes a label on the form “Audio CD - N tracks”. Finally, a new boolean tag, “audio”, has been added. It is set to true for audio CD “partitions”, and to false in all other cases. --- modules/removables.c | 147 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 121 insertions(+), 26 deletions(-) diff --git a/modules/removables.c b/modules/removables.c index 736317f..6727afa 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -36,8 +36,8 @@ struct partition { char *label; uint64_t size; + bool audio_cd; - /*tll(char *) mount_points;*/ mount_point_list_t mount_points; }; @@ -142,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); @@ -278,6 +279,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); @@ -296,7 +355,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); @@ -351,8 +412,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); @@ -374,8 +443,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); } @@ -409,31 +482,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; } From 52e2540d42d870aed38b6832fe73596bc5a1db8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 26 Dec 2021 12:25:00 +0100 Subject: [PATCH 43/84] =?UTF-8?q?doc:=20yambar-modules-removables:=20add?= =?UTF-8?q?=20=E2=80=98audio=E2=80=99=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/yambar-modules-removables.5.scd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/yambar-modules-removables.5.scd b/doc/yambar-modules-removables.5.scd index 4b984fa..cbffebe 100644 --- a/doc/yambar-modules-removables.5.scd +++ b/doc/yambar-modules-removables.5.scd @@ -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?*) From 0aff641d0cd2b6bf05f42306f53df0b6de5cf4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 26 Dec 2021 12:25:55 +0100 Subject: [PATCH 44/84] changelog: audio CD support --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a834b7..29d3d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ (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 + (https://codeberg.org/dnkl/yambar/issues/146). +* removables: new boolean tag: `audio`. ### Changed From 2a0a722c1346d87d47ffbdf7d516bbcd29548dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 19 Dec 2021 21:22:51 +0100 Subject: [PATCH 45/84] bar/wayland: handle layer surface being closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the output we’re mapped on is disabled (or disconnected), the compositor will unmap us. Up until now, our response was to simply shutdown. Now, we destroy the surface, remove all pending rendering buffers, and all further calls to commit() will return immediately, without doing anything. If the user has configured a specific monitor to use, we wait for that output to come back. When it does, we re-create the layer surface and then we’re up and running again. Bars running on the “default” monitor are handled in a similar way. Since we don’t have an output name from the configuration, we instead store the name of the output we were mapped on, when we’re either unmapped from that output, or that output global is destroyed. As soon as we see that output come back, we re-create the layer surface. --- bar/wayland.c | 245 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 170 insertions(+), 75 deletions(-) diff --git a/bar/wayland.c b/bar/wayland.c index a5a6731..89e5183 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -46,6 +46,7 @@ struct monitor { struct wl_output *output; struct zxdg_output_v1 *xdg; char *name; + uint32_t wl_name; int x; int y; @@ -96,6 +97,7 @@ struct wayland_backend { tll(struct monitor) monitors; const struct monitor *monitor; + char *last_mapped_monitor; int scale; @@ -113,6 +115,7 @@ 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; @@ -519,6 +522,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 +537,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 +643,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 +713,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 +751,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 +762,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 +1004,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 +1056,6 @@ update_size(struct wayland_backend *backend) return true; } -static const struct wl_surface_listener surface_listener; - static bool setup(struct bar *_bar) { @@ -989,45 +1101,14 @@ 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); - - if (!update_size(backend)) - return false; - assert(backend->monitor == NULL || backend->width / backend->monitor->scale <= backend->monitor->width_px); @@ -1051,16 +1132,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); @@ -1071,6 +1142,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); @@ -1079,12 +1151,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) @@ -1209,7 +1289,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 = { @@ -1233,7 +1321,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; @@ -1248,6 +1338,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 @@ -1262,6 +1353,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); @@ -1289,6 +1383,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); From 56c4a1c751832c23bba0d9aef3932d59a3dcdd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 25 Dec 2021 21:11:11 +0100 Subject: [PATCH 46/84] bar/wayland: add support for new events in wl-output v4 * name() * description() --- bar/wayland.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/bar/wayland.c b/bar/wayland.c index 89e5183..7894d00 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -495,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 @@ -1431,7 +1454,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; @@ -1446,5 +1469,5 @@ const struct backend wayland_backend_iface = { .commit = &commit, .refresh = &refresh, .set_cursor = &set_cursor, - .output_name = &output_name, + .output_name = &bar_output_name, }; From f053ddff7db9a829706d5d0f2238ba1412c5fa9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 1 Jan 2022 11:42:12 +0100 Subject: [PATCH 47/84] changelog: bar does not exit when monitor is disabled/unplugged --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d3d42..fa7a875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ * 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 (https://codeberg.org/dnkl/yambar/issues/106). ### Deprecated From 1d9297593e0d78d87a88e74aaed83c8cff285e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 Jan 2022 23:12:52 +0100 Subject: [PATCH 48/84] bar/wayland: error handling when dispatching Wayland events --- bar/wayland.c | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/bar/wayland.c b/bar/wayland.c index 7894d00..a57fdfa 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -1212,13 +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_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) { @@ -1230,16 +1236,13 @@ 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; } @@ -1253,9 +1256,10 @@ loop(struct bar *_bar, 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"); - break; + goto out; } assert(command == 1); @@ -1271,15 +1275,33 @@ loop(struct bar *_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 From 7945a561d0d53099735707ee8d4ab33c821090d0 Mon Sep 17 00:00:00 2001 From: "Soc Virnyl S. Estela" Date: Mon, 17 Jan 2022 09:58:11 +0800 Subject: [PATCH 49/84] Add river-tags example --- examples/configurations/river-tags.conf | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/configurations/river-tags.conf diff --git a/examples/configurations/river-tags.conf b/examples/configurations/river-tags.conf new file mode 100644 index 0000000..94e5874 --- /dev/null +++ b/examples/configurations/river-tags.conf @@ -0,0 +1,60 @@ +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 + tag: id + default: {string: {text: , font: *hack}} + values: + 1: {string: {text: ﳐ, font: *hack}} + 2: {string: {text: , font: *hack}} + 3: {string: {text: , font: *hack}} + 4: {string: {text: , font: *hack}} + 5: {string: {text: , font: *hack}} + 10: {string: {text: "scratchpad", font: *hack}} + 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)))" + tag: state + values: + urgent: + map: + <<: *river_base + deco: {background: {color: D08770ff}} + focused: + map: + <<: *river_base + deco: *bg_default + visible: + map: + tag: occupied + values: + false: {map: {<<: *river_base}} + true: {map: {<<: *river_base, deco: *bg_default}} + unfocused: + map: + <<: *river_base + invisible: + map: + tag: occupied + values: + false: {empty: {}} + true: {map: {<<: *river_base, deco: {underline: {size: 3, color: ea6962ff}}}} From 52e0b5f3d1bb8337b530d02e5eeecd72a32632e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 1 Feb 2022 19:01:04 +0100 Subject: [PATCH 50/84] tag: fix inverted logic for KiB vs KB logic --- CHANGELOG.md | 1 + tag.c | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa7a875..1a11471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ allowed, leading to various bad things; including yambar crashing, or worse, the compositor crashing (https://codeberg.org/dnkl/yambar/issues/129). +* kib/kb, mib/mb and gib/gb formatters were inverted. ### Security diff --git a/tag.c b/tag.c index 2943423..66b7f02 100644 --- a/tag.c +++ b/tag.c @@ -545,12 +545,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]; From 4fea561c6c0b899c8874cbd2d09abfd2ee6a9cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Feb 2022 12:50:39 +0100 Subject: [PATCH 51/84] log: fold long line --- log.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/log.c b/log.c index 467b6fd..5864853 100644 --- a/log.c +++ b/log.c @@ -40,7 +40,9 @@ log_init(enum log_colorize _colorize, bool _do_syslog, [LOG_FACILITY_DAEMON] = LOG_DAEMON, }; - colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO); + colorize = _colorize == LOG_COLORIZE_NEVER + ? false : _colorize == LOG_COLORIZE_ALWAYS + ? true : isatty(STDERR_FILENO); do_syslog = _do_syslog; log_level = _log_level; From ff238e62ba44bd749cc671d6b409f7203c46f15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 3 Sep 2021 20:44:22 +0200 Subject: [PATCH 52/84] fcft: adapt to API changes in fcft-3.x --- .builds/alpine-x64.yml | 5 +++ .gitlab-ci.yml | 20 +++++++++++ .woodpecker.yml | 1 + CHANGELOG.md | 2 +- PKGBUILD | 2 +- PKGBUILD.wayland-only | 2 +- char32.c | 78 ++++++++++++++++++++++++++++++++++++++++++ char32.h | 7 ++++ main.c | 5 +-- meson.build | 3 +- particles/string.c | 27 +++++++-------- 11 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 char32.c create mode 100644 char32.h diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index a5d4e9c..687af0e 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -37,6 +37,11 @@ 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 fcft && git checkout 3.0-dev && cd .. + 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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06df201..e5762c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,10 @@ versions: debug: stage: build script: + - cd subprojects + - git clone https://codeberg.org/dnkl/fcft.git + - cd fcft && git checkout 3.0-dev && cd .. + - cd .. - apk add gcovr - mkdir -p bld/debug - cd bld/debug @@ -55,6 +59,10 @@ debug: release: stage: build script: + - cd subprojects + - git clone https://codeberg.org/dnkl/fcft.git + - cd fcft && git checkout 3.0-dev && cd .. + - cd .. - mkdir -p bld/release - cd bld/release - meson --buildtype=minsize ../../ @@ -64,6 +72,10 @@ release: x11_only: stage: build script: + - cd subprojects + - git clone https://codeberg.org/dnkl/fcft.git + - cd fcft && git checkout 3.0-dev && cd .. + - cd .. - mkdir -p bld/debug - cd bld/debug - meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../ @@ -73,6 +85,10 @@ x11_only: wayland_only: stage: build script: + - cd subprojects + - git clone https://codeberg.org/dnkl/fcft.git + - cd fcft && git checkout 3.0-dev && cd .. + - cd .. - mkdir -p bld/debug - cd bld/debug - meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../ @@ -82,6 +98,10 @@ wayland_only: plugins_as_shared_modules: stage: build script: + - cd subprojects + - git clone https://codeberg.org/dnkl/fcft.git + - cd fcft && git checkout 3.0-dev && cd .. + - cd .. - mkdir -p bld/debug - cd bld/debug - meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../ diff --git a/.woodpecker.yml b/.woodpecker.yml index 4ea84f1..7c0a7ba 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -22,6 +22,7 @@ pipeline: - mkdir -p subprojects && cd subprojects - git clone https://codeberg.org/dnkl/tllist.git - git clone https://codeberg.org/dnkl/fcft.git + - cd fcft && git checkout 3.0-dev && cd .. - cd .. x64: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a11471..5d47017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### Changed +* fcft >= 3.0 is now required. * Made `libmpdclient` an optional dependency * battery: unknown battery states are now mapped to ‘unknown’, instead of ‘discharging’. @@ -85,7 +86,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. diff --git a/PKGBUILD b/PKGBUILD index e4b343b..42bf855 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -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=() diff --git a/PKGBUILD.wayland-only b/PKGBUILD.wayland-only index d0963e4..a3bbd5d 100644 --- a/PKGBUILD.wayland-only +++ b/PKGBUILD.wayland-only @@ -16,7 +16,7 @@ depends=( 'libudev.so' 'json-c' 'libmpdclient' - 'fcft>=2.4.0') + 'fcft>=3.0.0' 'fcft<4.0.0') source=() pkgver() { diff --git a/char32.c b/char32.c new file mode 100644 index 0000000..4673779 --- /dev/null +++ b/char32.c @@ -0,0 +1,78 @@ +#include "char32.h" + +#include +#include +#include + +#include + +#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; +} diff --git a/char32.h b/char32.h new file mode 100644 index 0000000..b01a7d3 --- /dev/null +++ b/char32.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +size_t c32len(const char32_t *s); +char32_t *ambstoc32(const char *src); diff --git a/main.c b/main.c index 49d3985..7aab81a 100644 --- a/main.c +++ b/main.c @@ -296,8 +296,9 @@ main(int argc, char *const *argv) "fcft log level enum offset"); _Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch"); - fcft_log_init( - (enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level); + fcft_init((enum fcft_log_colorize)log_colorize, log_syslog, + (enum fcft_log_class)log_level); + atexit(&fcft_fini); const struct sigaction sa = {.sa_handler = &signal_handler}; sigaction(SIGINT, &sa, NULL); diff --git a/meson.build b/meson.build index 66f200c..7b4aa3b 100644 --- a/meson.build +++ b/meson.build @@ -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'] + @@ -111,6 +111,7 @@ version = custom_target( yambar = executable( 'yambar', + 'char32.c', 'char32.h', 'color.h', 'config-verify.c', 'config-verify.h', 'config.c', 'config.h', diff --git a/particles/string.c b/particles/string.c index f6083f6..c5ade9f 100644 --- a/particles/string.c +++ b/particles/string.c @@ -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; } } @@ -206,7 +203,7 @@ 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( + struct fcft_text_run *run = fcft_rasterize_text_run_utf32( font, chars, wtext, FCFT_SUBPIXEL_NONE); if (run != NULL) { @@ -250,7 +247,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) From 227f9c608adec7b9b23316ac359e0896124eccf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Feb 2022 17:54:39 +0100 Subject: [PATCH 53/84] ci: use fcft master branch --- .builds/alpine-x64.yml | 1 - .gitlab-ci.yml | 5 ----- .woodpecker.yml | 1 - 3 files changed, 7 deletions(-) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml index 687af0e..fc657ce 100644 --- a/.builds/alpine-x64.yml +++ b/.builds/alpine-x64.yml @@ -40,7 +40,6 @@ tasks: - fcft: | cd yambar/subprojects git clone https://codeberg.org/dnkl/fcft.git - cd fcft && git checkout 3.0-dev && cd .. cd ../.. - setup: | mkdir -p bld/debug bld/release bld/x11-only bld/wayland-only bld/plugs-are-shared diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e5762c1..be21159 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,6 @@ debug: script: - cd subprojects - git clone https://codeberg.org/dnkl/fcft.git - - cd fcft && git checkout 3.0-dev && cd .. - cd .. - apk add gcovr - mkdir -p bld/debug @@ -61,7 +60,6 @@ release: script: - cd subprojects - git clone https://codeberg.org/dnkl/fcft.git - - cd fcft && git checkout 3.0-dev && cd .. - cd .. - mkdir -p bld/release - cd bld/release @@ -74,7 +72,6 @@ x11_only: script: - cd subprojects - git clone https://codeberg.org/dnkl/fcft.git - - cd fcft && git checkout 3.0-dev && cd .. - cd .. - mkdir -p bld/debug - cd bld/debug @@ -87,7 +84,6 @@ wayland_only: script: - cd subprojects - git clone https://codeberg.org/dnkl/fcft.git - - cd fcft && git checkout 3.0-dev && cd .. - cd .. - mkdir -p bld/debug - cd bld/debug @@ -100,7 +96,6 @@ plugins_as_shared_modules: script: - cd subprojects - git clone https://codeberg.org/dnkl/fcft.git - - cd fcft && git checkout 3.0-dev && cd .. - cd .. - mkdir -p bld/debug - cd bld/debug diff --git a/.woodpecker.yml b/.woodpecker.yml index 7c0a7ba..4ea84f1 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -22,7 +22,6 @@ pipeline: - mkdir -p subprojects && cd subprojects - git clone https://codeberg.org/dnkl/tllist.git - git clone https://codeberg.org/dnkl/fcft.git - - cd fcft && git checkout 3.0-dev && cd .. - cd .. x64: From 973aa929c1346dbe7ff74847d4a00b8f154d6fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Feb 2022 17:57:17 +0100 Subject: [PATCH 54/84] changelog: prepare for 1.8.0 --- CHANGELOG.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d47017..dc5a277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 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) @@ -8,7 +8,8 @@ * [1.5.0](#1-5-0) -## Unreleased +## Unrelease1.8.0 + ### Added * ramp: can now have custom min and max values @@ -33,8 +34,6 @@ disabled/unplugged (https://codeberg.org/dnkl/yambar/issues/106). -### Deprecated -### Removed ### Fixed * `left-margin` and `right-margin` from being rejected as invalid @@ -54,10 +53,21 @@ * kib/kb, mib/mb and gib/gb formatters were inverted. -### Security ### 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 From 5edc226c0094e2ad5c7882c43ae2efeb837b75fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Feb 2022 17:57:40 +0100 Subject: [PATCH 55/84] meson/pkgbuild: bump version to 1.8.0 --- PKGBUILD | 2 +- PKGBUILD.wayland-only | 2 +- meson.build | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 42bf855..13ddc83 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,5 +1,5 @@ pkgname=yambar -pkgver=1.7.0 +pkgver=1.8.0 pkgrel=1 pkgdesc="Simplistic and highly configurable status panel for X and Wayland" arch=('x86_64' 'aarch64') diff --git a/PKGBUILD.wayland-only b/PKGBUILD.wayland-only index a3bbd5d..5dc6cfd 100644 --- a/PKGBUILD.wayland-only +++ b/PKGBUILD.wayland-only @@ -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') diff --git a/meson.build b/meson.build index 7b4aa3b..4a479ec 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('yambar', 'c', - version: '1.7.0', + version: '1.8.0', license: 'MIT', meson_version: '>=0.53.0', default_options: ['c_std=c18', From 3cc142a27302adbb4a314ea95c901a5216b06d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Feb 2022 18:00:01 +0100 Subject: [PATCH 56/84] changelog: fix 1.8.0 header --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5a277..ab31475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * [1.5.0](#1-5-0) -## Unrelease1.8.0 +## 1.8.0 ### Added From 2cfe45ee812407dae5fdafe4f82510cdaa024c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 5 Feb 2022 18:02:50 +0100 Subject: [PATCH 57/84] =?UTF-8?q?changelog:=20add=20new=20=E2=80=98unrelea?= =?UTF-8?q?sed=E2=80=99=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab31475..58b6346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.8.0](#1-8-0) * [1.7.0](#1-7-0) * [1.6.2](#1-6-2) @@ -8,6 +9,16 @@ * [1.5.0](#1-5-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.8.0 ### Added From 6ac046dec31353cce847fb7a6b1c4cc99aedd84f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 10 Feb 2022 18:30:19 +0100 Subject: [PATCH 58/84] config: implement font fallback Fonts in the configuration may now be a comma separated list of fonts (all using the fontconfig format). The first font is the primary font, and the rest are fallback fonts that will be searched, in order. --- CHANGELOG.md | 3 +++ config.c | 34 +++++++++++++++++++++++++++++++++- doc/yambar.5.scd | 9 +++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b6346..43c95a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ ## Unreleased ### Added + +* Support for custom font fallbacks + (https://codeberg.org/dnkl/yambar/issues/153). ### Changed ### Deprecated ### Removed diff --git a/config.c b/config.c index 7f9583e..580781b 100644 --- a/config.c +++ b/config.c @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -66,7 +67,38 @@ 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; } struct deco * diff --git a/doc/yambar.5.scd b/doc/yambar.5.scd index d3a585f..4e70ef2 100644 --- a/doc/yambar.5.scd +++ b/doc/yambar.5.scd @@ -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,11 @@ 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. | foreground : color : no From 605490c872d637d95424b8add5ad12f22263c417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 10 Feb 2022 18:35:45 +0100 Subject: [PATCH 59/84] overline: new decoration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to the ‘underline’ decoration --- CHANGELOG.md | 4 ++ decorations/meson.build | 2 +- decorations/overline.c | 72 ++++++++++++++++++++++++++++++++++++ doc/yambar-decorations.5.scd | 32 ++++++++++++++++ plugin.c | 2 + 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 decorations/overline.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c95a1..1f8113d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ * Support for custom font fallbacks (https://codeberg.org/dnkl/yambar/issues/153). +* overline: new decoration + (https://codeberg.org/dnkl/yambar/issues/153). + + ### Changed ### Deprecated ### Removed diff --git a/decorations/meson.build b/decorations/meson.build index e8b289c..c64164c 100644 --- a/decorations/meson.build +++ b/decorations/meson.build @@ -1,7 +1,7 @@ deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft]) decorations = [] -foreach deco : ['background', 'border', '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, diff --git a/decorations/overline.c b/decorations/overline.c new file mode 100644 index 0000000..1beb896 --- /dev/null +++ b/decorations/overline.c @@ -0,0 +1,72 @@ +#include + +#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 diff --git a/doc/yambar-decorations.5.scd b/doc/yambar-decorations.5.scd index 8c972f2..24282b5 100644 --- a/doc/yambar-decorations.5.scd +++ b/doc/yambar-decorations.5.scd @@ -71,6 +71,38 @@ content: ``` +# 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 diff --git a/plugin.c b/plugin.c index 1faae0e..43e8672 100644 --- a/plugin.c +++ b/plugin.c @@ -63,6 +63,7 @@ EXTERN_DECORATION(background); EXTERN_DECORATION(border); EXTERN_DECORATION(stack); EXTERN_DECORATION(underline); +EXTERN_DECORATION(overline); #undef EXTERN_DECORATION #undef EXTERN_PARTICLE @@ -152,6 +153,7 @@ init(void) 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 From a2cf05a64d00b14c0d5fbe001d6cb82aca59df81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 11 Feb 2022 21:44:43 +0100 Subject: [PATCH 60/84] =?UTF-8?q?module/i3:=20add=20=E2=80=98strip-workspa?= =?UTF-8?q?ce-numbers=E2=80=99=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a boolean option. When set, “N:” prefixes will be stripped from the workspaces’ name tag, *after* having been sorted (if the ‘sort’ option is being used). This makes it useful to arrange the workspaces in a fixed order, by prefixing the names with a number in the Sway config: set $ws1 “1:xyz” set $ws2 “2:abc” Then, in the yambar config: i3: sort: ascending strip-workspace-numbers: true --- CHANGELOG.md | 1 + doc/yambar-modules-i3.5.scd | 4 ++++ modules/i3.c | 34 +++++++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f8113d..18ea8b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ (https://codeberg.org/dnkl/yambar/issues/153). * overline: new decoration (https://codeberg.org/dnkl/yambar/issues/153). +* i3/sway: boolean option `strip-workspace-numbers`. ### Changed diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd index 2cae2f8..b2ba394 100644 --- a/doc/yambar-modules-i3.5.scd +++ b/doc/yambar-modules-i3.5.scd @@ -68,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 diff --git a/modules/i3.c b/modules/i3.c index 56dec4d..fcd9395 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -60,6 +60,7 @@ struct private { size_t count; } ws_content; + bool strip_workspace_numbers; enum sort_mode sort_mode; tll(struct workspace) workspaces; @@ -107,10 +108,11 @@ workspace_from_json(const struct json_object *json, struct workspace *ws) const size_t node_count = json_object_array_length(focus); 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), @@ -615,9 +617,16 @@ 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, }; @@ -725,9 +734,17 @@ content(struct module *mod) 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), @@ -778,7 +795,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)); @@ -794,6 +812,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; @@ -821,6 +840,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; @@ -859,7 +880,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 @@ -914,6 +937,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node) {"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}, From ca407cd1669e6c871a25c86db92ea8e121fda46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 14 Feb 2022 18:33:14 +0100 Subject: [PATCH 61/84] module/i3: treat workspaces on the form N:name as numerical --- modules/i3.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/i3.c b/modules/i3.c index fcd9395..bb51794 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -72,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; From c44970717b52c1678b6a8dabdeda2ca75a42cdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 15 Feb 2022 21:14:08 +0100 Subject: [PATCH 62/84] module/i3: workspace::focus is apparently Sway only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On i3, users are currently greeted with: err: modules/i3.c:94: workspace reply/event without 'name' and/or 'output', and/or 'focus' properties This patch makes ‘focus’ an optional attribute. When missing, we assume a node-count of 0, which means the workspace’s ‘empty’ tag will always be true. Document this in the i3 man page. --- CHANGELOG.md | 6 ++++++ doc/yambar-modules-i3.5.scd | 2 +- modules/i3.c | 17 +++++++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ea8b4..63f48f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,12 @@ ### Deprecated ### Removed ### Fixed + +* i3: fixed “missing workspace indicator” (_err: modules/i3.c:94: + workspace reply/event without 'name' and/or 'output', and/or 'focus' + properties_). + + ### Security ### Contributors diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd index b2ba394..9184e46 100644 --- a/doc/yambar-modules-i3.5.scd +++ b/doc/yambar-modules-i3.5.scd @@ -37,7 +37,7 @@ with the _application_ and _title_ tags to replace the X11-only : True if the workspace has the urgent flag set | empty : bool -: True if the workspace is empty +: True if the workspace is empty (Sway only) | state : string : One of *urgent*, *focused*, *unfocused* or *invisible* (note: diff --git a/modules/i3.c b/modules/i3.c index bb51794..a7f3cc0 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -103,16 +103,18 @@ static bool workspace_from_json(const struct json_object *json, struct workspace *ws) { /* Always present */ - struct json_object *name, *output, *focus; + struct json_object *name, *output; if (!json_object_object_get_ex(json, "name", &name) || - !json_object_object_get_ex(json, "output", &output) || - !json_object_object_get_ex(json, "focus", &focus)) + !json_object_object_get_ex(json, "output", &output)) { - LOG_ERR("workspace reply/event without 'name' and/or 'output', " - "and/or 'focus' properties"); + 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; @@ -122,7 +124,10 @@ 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 = json_object_array_length(focus); + 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); From 265188ca4cb4ec261ca145fa8c874bd3f65a1e73 Mon Sep 17 00:00:00 2001 From: Leonardo Neumann Date: Mon, 21 Feb 2022 02:18:54 -0300 Subject: [PATCH 63/84] char32: add missing header to work with musl --- char32.c | 1 + 1 file changed, 1 insertion(+) diff --git a/char32.c b/char32.c index 4673779..71d2ae5 100644 --- a/char32.c +++ b/char32.c @@ -5,6 +5,7 @@ #include #include +#include #define LOG_MODULE "char32" #define LOG_ENABLE_DBG 0 From ffccabbb13cdd8e681b739e0e93a2466e858f2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 23 Feb 2022 18:43:13 +0100 Subject: [PATCH 64/84] =?UTF-8?q?config:=20add=20inheritable=20option=20?= =?UTF-8?q?=E2=80=9Cfont-shaping=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds an inheritable option, “font-shaping”, that controls whether a particle that renders text should enable font-shaping or not. The option works similar to the ‘font’ option: one can set it at the top-level, and it gets inherited down through all modules and to their particles. Or, you can set it on a module and it gets inherited to all its particles, but not to other modules’ particles. Finally, you can set it on individual particles, in which case it only applies to them (or “child” particles). When font-shaping is enabled (the default), the string particle shapes full text runs using the fcft_rasterize_text_run_utf32() API. In fcft, this results in HarfBuzz being used to shape the string. When disabled, the string particle instead uses the simpler fcft_rasterize_char_utf32() API, which rasterizes individual characters. This gives user greater control over the font rendering. One example is bitmap fonts, which HarfBuzz often doesn’t get right. Closes #159 --- CHANGELOG.md | 3 ++ bar/bar.h | 2 ++ config-verify.c | 8 ++++++ config-verify.h | 1 + config.c | 56 ++++++++++++++++++++++++++++++++++++-- config.h | 3 ++ doc/yambar-particles.5.scd | 6 ++++ doc/yambar.5.scd | 6 ++++ font-shaping.h | 7 +++++ meson.build | 1 + particle.c | 5 ++-- particle.h | 6 +++- particles/list.c | 2 +- particles/map.c | 1 + particles/progress-bar.c | 1 + particles/ramp.c | 2 +- particles/string.c | 4 ++- 17 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 font-shaping.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f48f6..db94656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ * overline: new decoration (https://codeberg.org/dnkl/yambar/issues/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 + (https://codeberg.org/dnkl/yambar/issues/159). ### Changed diff --git a/bar/bar.h b/bar/bar.h index c9f94f4..717b690 100644 --- a/bar/bar.h +++ b/bar/bar.h @@ -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; diff --git a/config-verify.c b/config-verify.c index b92fb71..68a50c8 100644 --- a/config-verify.c +++ b/config-verify.c @@ -228,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) { @@ -450,6 +457,7 @@ conf_verify_bar(const struct yml_node *bar) {"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}, diff --git a/config-verify.h b/config-verify.h index 8e5ab29..0a4ae34 100644 --- a/config-verify.h +++ b/config-verify.h @@ -45,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); diff --git a/config.c b/config.c index 580781b..d2c11a6 100644 --- a/config.c +++ b/config.c @@ -101,6 +101,44 @@ conf_to_font(const struct yml_node *node) 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 * conf_to_deco(const struct yml_node *node) { @@ -144,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); } @@ -163,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"); @@ -215,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); @@ -237,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, }; /* @@ -359,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"); @@ -367,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, }; @@ -402,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, }; diff --git a/config.h b/config.h index 56f5b2e..ceb4b85 100644 --- a/config.h +++ b/config.h @@ -3,6 +3,7 @@ #include #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; }; diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index a633269..312fb99 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -31,6 +31,12 @@ 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 diff --git a/doc/yambar.5.scd b/doc/yambar.5.scd index 4e70ef2..35c87b8 100644 --- a/doc/yambar.5.scd +++ b/doc/yambar.5.scd @@ -130,6 +130,12 @@ types that are frequently used: 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 diff --git a/font-shaping.h b/font-shaping.h new file mode 100644 index 0000000..3ae3817 --- /dev/null +++ b/font-shaping.h @@ -0,0 +1,7 @@ +#pragma once + +enum font_shaping { + FONT_SHAPE_NONE, + FONT_SHAPE_GRAPHEMES, + FONT_SHAPE_FULL, +}; diff --git a/meson.build b/meson.build index 4a479ec..b43d3fb 100644 --- a/meson.build +++ b/meson.build @@ -116,6 +116,7 @@ yambar = executable( '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', diff --git a/particle.c b/particle.c index 1b06f9a..7fd747c 100644 --- a/particle.c +++ b/particle.c @@ -31,14 +31,15 @@ particle_default_destroy(struct particle *particle) struct particle * particle_common_new(int left_margin, int right_margin, const char **on_click_templates, - struct fcft_font *font, pixman_color_t foreground, - struct deco *deco) + struct fcft_font *font, enum font_shaping font_shaping, + pixman_color_t foreground, struct deco *deco) { struct particle *p = calloc(1, sizeof(*p)); p->left_margin = left_margin; p->right_margin = right_margin; p->foreground = foreground; p->font = font; + p->font_shaping = font_shaping; p->deco = deco; if (on_click_templates != NULL) { diff --git a/particle.h b/particle.h index 7c5685e..bdf01f2 100644 --- a/particle.h +++ b/particle.h @@ -5,6 +5,7 @@ #include "color.h" #include "decoration.h" +#include "font-shaping.h" #include "tag.h" enum mouse_event { @@ -35,6 +36,7 @@ struct particle { pixman_color_t foreground; struct fcft_font *font; + enum font_shaping font_shaping; struct deco *deco; void (*destroy)(struct particle *particle); @@ -61,7 +63,8 @@ struct exposable { struct particle *particle_common_new( int left_margin, int right_margin, const char *on_click_templates[], - struct fcft_font *font, pixman_color_t foreground, struct deco *deco); + struct fcft_font *font, enum font_shaping font_shaping, + pixman_color_t foreground, struct deco *deco); void particle_default_destroy(struct particle *particle); @@ -82,6 +85,7 @@ void exposable_default_on_mouse( {"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} diff --git a/particles/list.c b/particles/list.c index e5cf883..a2c37c6 100644 --- a/particles/list.c +++ b/particles/list.c @@ -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); diff --git a/particles/map.c b/particles/map.c index 980bdd1..abf76a6 100644 --- a/particles/map.c +++ b/particles/map.c @@ -215,6 +215,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 }; diff --git a/particles/progress-bar.c b/particles/progress-bar.c index 462d536..f9e3999 100644 --- a/particles/progress-bar.c +++ b/particles/progress-bar.c @@ -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, }; diff --git a/particles/ramp.c b/particles/ramp.c index 7815d99..0127519 100644 --- a/particles/ramp.c +++ b/particles/ramp.c @@ -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; diff --git a/particles/string.c b/particles/string.c index c5ade9f..f1e1faf 100644 --- a/particles/string.c +++ b/particles/string.c @@ -202,7 +202,9 @@ 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) { + 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); From 4daa3d99047628608e6e0e094b5a78c05e90f120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 27 Feb 2022 11:32:46 +0100 Subject: [PATCH 65/84] meson: stop using deprecated functions, require meson >= 0.58 * get_pkgconfig_variable() -> get_variable() * prog.path() -> prog.full_path() * meson.build_root() -> meson.global_build_root() --- CHANGELOG.md | 4 ++++ bar/meson.build | 4 ++-- doc/meson.build | 4 ++-- meson.build | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db94656..01d9ab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ ### Changed + +* Minimum required meson version is now 0.58. + + ### Deprecated ### Removed ### Fixed diff --git a/bar/meson.build b/bar/meson.build index 90f050b..6ca5ec9 100644 --- a/bar/meson.build +++ b/bar/meson.build @@ -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 = [] diff --git a/doc/meson.build b/doc/meson.build index 0d7f873..e882f52 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -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', @@ -25,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))) diff --git a/meson.build b/meson.build index b43d3fb..ef28c58 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('yambar', 'c', 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 From 3ff1c95208c2a933d193f38cbd75dbc896a6776f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Mar 2022 10:50:57 +0100 Subject: [PATCH 66/84] char32: only include stdc-predef.h if it is available Use the (relatively new) macro __has_include() to check if stdc-predef.h exists, and only include it if it does. If stdc-predef.h does not exist, or if the compiler does not implement __has_include(), stdc-predef.h is *not* included. --- char32.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/char32.c b/char32.c index 71d2ae5..0ca029a 100644 --- a/char32.c +++ b/char32.c @@ -5,7 +5,12 @@ #include #include -#include + +#if defined __has_include + #if __has_include () + #include + #endif +#endif #define LOG_MODULE "char32" #define LOG_ENABLE_DBG 0 From 4bb81e89406077b8402630f240d244ce446dfa54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 29 Mar 2022 18:21:13 +0200 Subject: [PATCH 67/84] modules: add SOCK_CLOEXEC to all socket() calls --- modules/i3.c | 2 +- modules/mpd.c | 2 +- modules/network.c | 4 ++-- modules/sway-xkb.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/i3.c b/modules/i3.c index a7f3cc0..22b6d4b 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -621,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; diff --git a/modules/mpd.c b/modules/mpd.c index b2e9e6e..27992d3 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -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); diff --git a/modules/network.c b/modules/network.c index 3bff716..34980fa 100644 --- a/modules/network.c +++ b/modules/network.c @@ -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; diff --git a/modules/sway-xkb.c b/modules/sway-xkb.c index 9841feb..6632ab7 100644 --- a/modules/sway-xkb.c +++ b/modules/sway-xkb.c @@ -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; From 2b6f5b1e36c8c38943736a1853ad469aac3e94d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 29 Mar 2022 18:21:44 +0200 Subject: [PATCH 68/84] module/removables: open /proc/self/mountinfo with CLOEXEC --- modules/removables.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/removables.c b/modules/removables.c index 6727afa..d8fe7c9 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -164,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; @@ -641,7 +647,7 @@ run(struct module *mod) /* To be able to poll() mountinfo for changes, to detect * mount/unmount operations */ - int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY); + int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); int ret = 1; From 068c25d8f6db0cbd0aa4668f45b2135a4b83f8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 29 Mar 2022 18:22:08 +0200 Subject: [PATCH 69/84] module/script: open comm-pipe + /dev/null with CLOEXEC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures we don’t leak FDs when exec:ing e.g. on-click handlers. Note that the comm-pipe FD is *supposed* to stay open when we execing the script. This is handled by the call to dup2(), which drops the CLOEXEC flag. Since dup2() is called after the fork, the dup:ed FD is never visible in the “parent” yambar process. --- modules/script.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/script.c b/modules/script.c index 254aac4..5b3b51a 100644 --- a/modules/script.c +++ b/modules/script.c @@ -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; From fd014dc33ba9285db30a5609352bc768978c9d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 29 Mar 2022 18:23:55 +0200 Subject: [PATCH 70/84] =?UTF-8?q?Don=E2=80=99t=20loop=2065536=20FDs,=20try?= =?UTF-8?q?ing=20to=20close=20them,=20when=20fork+exec:ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All FDs should now have the CLOEXEC flag set, and thus there’s no longer needed to manually loop “all” possible FDs and (trying to) close them. Note: the alsa module (alsalib, actually) is “racy” - while booting up, it temporarily opens the asoundrc file without CLOEXEC. If e.g. the script module starts its script inside this window, it’ll have a leaked FD. Not much we can do about it though :/ Closes #169 --- CHANGELOG.md | 3 +++ modules/script.c | 10 ---------- particle.c | 5 ----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01d9ab8..d6e8b90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ * 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 + (https://codeberg.org/dnkl/yambar/issues/169). ### Security diff --git a/modules/script.c b/modules/script.c index 5b3b51a..f701e99 100644 --- a/modules/script.c +++ b/modules/script.c @@ -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: diff --git a/particle.c b/particle.c index 7fd747c..5f6b04d 100644 --- a/particle.c +++ b/particle.c @@ -277,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: From fce2787bdf319b5464d2c37e05044abecb0fa78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 7 Apr 2022 13:21:41 +0200 Subject: [PATCH 71/84] module/cpu: use get_nprocs() to retrieve the CPU count --- modules/cpu.c | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/modules/cpu.c b/modules/cpu.c index 0967dfd..d85afc8 100644 --- a/modules/cpu.c +++ b/modules/cpu.c @@ -10,6 +10,8 @@ #include #include +#include + #define LOG_MODULE "cpu" #define LOG_ENABLE_DBG 0 #define SMALLEST_INTERVAL 500 @@ -55,34 +57,8 @@ description(struct module *mod) static uint32_t get_cpu_nb_cores() { - uint32_t nb_cores = 0; - FILE *fp = NULL; - char *line = NULL; - size_t len = 0; - ssize_t read; - - fp = fopen("/proc/cpuinfo", "r"); - if (NULL == fp) { - LOG_ERRNO("unable to open /proc/cpuinfo"); - return 0; - } - while ((read = getline(&line, &len, fp)) != -1) { - if (strncmp(line, "siblings", sizeof("siblings") - 1) == 0) { - char *pos = (char *)memchr(line, ':', read); - if (pos == NULL) { - LOG_ERR("unable to parse siblings field to find the number of cores"); - return 0; - } - errno = 0; - nb_cores = strtoul(pos + 1, NULL, 10); - if (errno == ERANGE) { - LOG_ERR("number of cores is out of range"); - } - break; - } - } - fclose(fp); - free(line); + int nb_cores = get_nprocs(); + LOG_DBG("CPU count: %d", nb_cores); return nb_cores; } From 62ca06eccbdb6627059af02edf8485243b71f2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 7 Apr 2022 13:28:35 +0200 Subject: [PATCH 72/84] =?UTF-8?q?module/cpu:=20don=E2=80=99t=20use=20core?= =?UTF-8?q?=20ID=20from=20/proc/stat=20as=20array=20index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /proc/stat lists CPU usage, in the form: cpu ... cpu0 ... cpu1 ... ... cpuN ... where the first line is a summary line. We’ve been using the CPU numbers from /proc/stat to index into our internal stats array. This doesn’t work on systems where the core IDs aren’t consecutive. Examples of such systems are SMT systems with SMT disabled. Here, /proc/stat may look like this instead: cpu ... cpu0 ... cpu2 ... cpu4 ... ... With this patch, we ignore the CPU ID from /proc/stat. Instead, we use a simple counter that is incremented for each (valid) cpu line found in /proc/stat. To protect against corrupt /proc/stat content, stop parsing /proc/stat if the number of parsed CPU lines exceed what we consider to the be total number of CPUs in the system. Closes #172 --- modules/cpu.c | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/modules/cpu.c b/modules/cpu.c index d85afc8..b4067cd 100644 --- a/modules/cpu.c +++ b/modules/cpu.c @@ -64,22 +64,27 @@ get_cpu_nb_cores() } static bool -parse_proc_stat_line(const char *line, int32_t *core, uint32_t *user, uint32_t *nice, uint32_t *system, uint32_t *idle, +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); - *core = -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, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice); + 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; } } @@ -125,20 +130,23 @@ refresh_cpu_stats(struct cpu_stats *cpu_stats) return; } - while ((read = getline(&line, &len, fp)) != -1) { + while ((read = getline(&line, &len, fp)) != -1 && core <= nb_cores) { if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) { - if (!parse_proc_stat_line(line, &core, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, - &guest, &guestnice) - || core < -1 || core >= (int32_t)nb_cores) { + 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 + 1] = cpu_stats->cur_cores_idle[core + 1]; - cpu_stats->prev_cores_nidle[core + 1] = cpu_stats->cur_cores_nidle[core + 1]; + 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 + 1] = idle + iowait; - cpu_stats->cur_cores_nidle[core + 1] = user + nice + system + irq + softirq + steal; + cpu_stats->cur_cores_idle[core] = idle + iowait; + cpu_stats->cur_cores_nidle[core] = user + nice + system + irq + softirq + steal; + + core++; } } exit: From 9c28d368988300733073ef1a6891446ef8ad2c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 7 Apr 2022 13:33:41 +0200 Subject: [PATCH 73/84] =?UTF-8?q?changelog:=20cpu:=20don=E2=80=99t=20error?= =?UTF-8?q?=20out=20on=20systems=20with=20SMT=20disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6e8b90..7f1a713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## Unreleased + ### Added * Support for custom font fallbacks @@ -37,6 +38,8 @@ * Slow/laggy behavior when quickly spawning many `on-click` handlers, e.g. when handling mouse wheel events (https://codeberg.org/dnkl/yambar/issues/169). +* cpu: don’t error out on systems where SMT has been disabled + (https://codeberg.org/dnkl/yambar/issues/172). ### Security From c1e549df54ccf46b16e92676a351728e44f533dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 16 Apr 2022 11:59:10 +0200 Subject: [PATCH 74/84] examples: config: remove duplicate volume icons --- examples/configurations/laptop.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index 3a6c439..888b19e 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -239,8 +239,6 @@ bar: 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 From acb8a0937627c1692ccc73029948f14185ea3423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Apr 2022 20:23:08 +0200 Subject: [PATCH 75/84] changelog: turn all issue links into markdown hyperlinks --- CHANGELOG.md | 74 ++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f1a713..f317bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,13 @@ ### Added * Support for custom font fallbacks - (https://codeberg.org/dnkl/yambar/issues/153). + ([#153](https://codeberg.org/dnkl/yambar/issues/153)). * overline: new decoration - (https://codeberg.org/dnkl/yambar/issues/153). + ([#153](https://codeberg.org/dnkl/yambar/issues/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 - (https://codeberg.org/dnkl/yambar/issues/159). + ([#159](https://codeberg.org/dnkl/yambar/issues/159)). ### Changed @@ -37,9 +37,9 @@ properties_). * Slow/laggy behavior when quickly spawning many `on-click` handlers, e.g. when handling mouse wheel events - (https://codeberg.org/dnkl/yambar/issues/169). + ([#169](https://codeberg.org/dnkl/yambar/issues/169)). * cpu: don’t error out on systems where SMT has been disabled - (https://codeberg.org/dnkl/yambar/issues/172). + ([#172](https://codeberg.org/dnkl/yambar/issues/172)). ### Security @@ -51,14 +51,14 @@ ### Added * ramp: can now have custom min and max values - (https://codeberg.org/dnkl/yambar/issues/103). + ([#103](https://codeberg.org/dnkl/yambar/issues/103)). * border: new decoration. * i3/sway: new boolean tag: `empty` - (https://codeberg.org/dnkl/yambar/issues/139). + ([#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 - (https://codeberg.org/dnkl/yambar/issues/146). + ([#146](https://codeberg.org/dnkl/yambar/issues/146)). * removables: new boolean tag: `audio`. @@ -69,7 +69,7 @@ * battery: unknown battery states are now mapped to ‘unknown’, instead of ‘discharging’. * Wayland: the bar no longer exits when the monitor is - disabled/unplugged (https://codeberg.org/dnkl/yambar/issues/106). + disabled/unplugged ([#106](https://codeberg.org/dnkl/yambar/issues/106)). ### Fixed @@ -78,16 +78,16 @@ options. * Crash when `udev_monitor_receive_device()` returned `NULL`. This affected the “backlight”, “battery” and “removables” modules - (https://codeberg.org/dnkl/yambar/issues/109). + ([#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 (https://codeberg.org/dnkl/yambar/issues/116). + “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 - (https://codeberg.org/dnkl/yambar/issues/129). + ([#129](https://codeberg.org/dnkl/yambar/issues/129)). * kib/kb, mib/mb and gib/gb formatters were inverted. @@ -112,17 +112,17 @@ ### 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. @@ -143,7 +143,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 @@ -153,7 +153,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 @@ -169,39 +169,39 @@ * Text shaping support. * Support for middle and right mouse buttons, mouse wheel and trackpad - scrolling (https://codeberg.org/dnkl/yambar/issues/39). + scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)). * script: polling mode. See the new `poll-interval` option - (https://codeberg.org/dnkl/yambar/issues/67). + ([#67](https://codeberg.org/dnkl/yambar/issues/67)). ### Changed * doc: split up **yambar-modules**(5) into multiple man pages, one for - each module (https://codeberg.org/dnkl/yambar/issues/15). + each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)). * fcft >= 2.4.0 is now required. * sway-xkb: non-keyboard inputs are now ignored - (https://codeberg.org/dnkl/yambar/issues/51). + ([#51](https://codeberg.org/dnkl/yambar/issues/51)). * battery: don’t terminate (causing last status to “freeze”) when failing to update; retry again later - (https://codeberg.org/dnkl/yambar/issues/44). + ([#44](https://codeberg.org/dnkl/yambar/issues/44)). * battery: differentiate "Not Charging" and "Discharging" in state tag of battery module. - (https://codeberg.org/dnkl/yambar/issues/57). + ([#57](https://codeberg.org/dnkl/yambar/issues/57)). * string: use HORIZONTAL ELLIPSIS instead of three regular periods when truncating a string - (https://codeberg.org/dnkl/yambar/issues/73). + ([#73](https://codeberg.org/dnkl/yambar/issues/73)). ### Fixed * Crash when merging non-dictionary anchors in the YAML configuration - (https://codeberg.org/dnkl/yambar/issues/32). + ([#32](https://codeberg.org/dnkl/yambar/issues/32)). * Crash in the `ramp` particle when the tag’s value was out-of-bounds - (https://codeberg.org/dnkl/yambar/issues/45). + ([#45](https://codeberg.org/dnkl/yambar/issues/45)). * Crash when a string particle contained `{}` - (https://codeberg.org/dnkl/yambar/issues/48). + ([#48](https://codeberg.org/dnkl/yambar/issues/48)). * `script` module rejecting range tag end values containing the digit - `9` (https://codeberg.org/dnkl/yambar/issues/60). + `9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)). ### Contributors @@ -216,7 +216,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 @@ -224,7 +224,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 @@ -233,17 +233,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 @@ -253,12 +253,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 From 1206153b03ae53c47136ff531aa77ec873ff50e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 22 Apr 2022 20:25:11 +0200 Subject: [PATCH 76/84] =?UTF-8?q?changelog:=20convert=20all=20issue=20link?= =?UTF-8?q?s=20to=20reference=20links=20in=20=E2=80=98unreleased=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f317bef..758ddb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,14 +13,12 @@ ### Added -* Support for custom font fallbacks - ([#153](https://codeberg.org/dnkl/yambar/issues/153)). -* overline: new decoration - ([#153](https://codeberg.org/dnkl/yambar/issues/153)). +* 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](https://codeberg.org/dnkl/yambar/issues/159)). + ([#159][159]). ### Changed @@ -36,15 +34,19 @@ 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](https://codeberg.org/dnkl/yambar/issues/169)). + e.g. when handling mouse wheel events ([#169][169]). * cpu: don’t error out on systems where SMT has been disabled - ([#172](https://codeberg.org/dnkl/yambar/issues/172)). + ([#172][172]). ### Security ### Contributors +[153]: https://codeberg.org/dnkl/yambar/issues/153 +[159]: https://codeberg.org/dnkl/yambar/issues/159 +[169]: https://codeberg.org/dnkl/yambar/issues/169 +[172]: https://codeberg.org/dnkl/yambar/issues/172 + ## 1.8.0 From 4496d82cfbd62406933ad950152a64c443ec85a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 23 Apr 2022 11:14:50 +0200 Subject: [PATCH 77/84] changelog: hyperlink lists under their corresponding sub-section --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 758ddb1..da20255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ 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 @@ -38,15 +41,13 @@ * cpu: don’t error out on systems where SMT has been disabled ([#172][172]). +[169]: https://codeberg.org/dnkl/yambar/issues/169 +[172]: https://codeberg.org/dnkl/yambar/issues/172 + ### Security ### Contributors -[153]: https://codeberg.org/dnkl/yambar/issues/153 -[159]: https://codeberg.org/dnkl/yambar/issues/159 -[169]: https://codeberg.org/dnkl/yambar/issues/169 -[172]: https://codeberg.org/dnkl/yambar/issues/172 - ## 1.8.0 From 2b103b7acdbfe3748303d7ab313f3d51a56845f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Gibrowski=20Fa=C3=A9?= Date: Sun, 10 Apr 2022 00:10:07 -0300 Subject: [PATCH 78/84] Implement conditions on tag A condition is formed by: is the normal yambar tag. is one of '==', '!=', '<', '<=', '>', or '>='. is what you wish to compare it to. 'boolean' tags must be used directly. They falsehood is matched with '~': ~ Finally, to match an empty string, one must use ' "" ': "" --- doc/yambar-modules-foreign-toplevel.5.scd | 7 +- doc/yambar-modules-i3.5.scd | 5 +- doc/yambar-modules-removables.5.scd | 7 +- doc/yambar-modules-river.5.scd | 7 +- doc/yambar-modules-script.5.scd | 7 +- doc/yambar-modules.5.scd | 17 +- doc/yambar-particles.5.scd | 51 +++- particles/map.c | 349 ++++++++++++++++++++-- tag.c | 28 ++ tag.h | 8 + test/full-conf-good.yml | 5 +- 11 files changed, 416 insertions(+), 75 deletions(-) diff --git a/doc/yambar-modules-foreign-toplevel.5.scd b/doc/yambar-modules-foreign-toplevel.5.scd index 6a65ec3..5ef8c57 100644 --- a/doc/yambar-modules-foreign-toplevel.5.scd +++ b/doc/yambar-modules-foreign-toplevel.5.scd @@ -67,10 +67,9 @@ bar: - foreign-toplevel: content: map: - tag: activated - values: - false: {empty: {}} - true: + conditions: + (activated == false): {empty: {}} + (activated == true): - string: {text: "{app-id}: {title}"} ``` diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd index 9184e46..e646183 100644 --- a/doc/yambar-modules-i3.5.scd +++ b/doc/yambar-modules-i3.5.scd @@ -102,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}"}} ``` diff --git a/doc/yambar-modules-removables.5.scd b/doc/yambar-modules-removables.5.scd index cbffebe..5c34aef 100644 --- a/doc/yambar-modules-removables.5.scd +++ b/doc/yambar-modules-removables.5.scd @@ -74,13 +74,12 @@ bar: - removables: content: map: - tag: mounted - values: - false: + conditions: + (mounted == false): string: on-click: udisksctl mount -b {device} text: "{label}" - true: + (mounted == true): string: on-click: udisksctl unmount -b {device} text: "{label}" diff --git a/doc/yambar-modules-river.5.scd b/doc/yambar-modules-river.5.scd index 96be605..2255dc0 100644 --- a/doc/yambar-modules-river.5.scd +++ b/doc/yambar-modules-river.5.scd @@ -79,10 +79,9 @@ bar: title: {string: { text: "{seat} - {title}" }} content: map: - tag: occupied - values: - false: {empty: {}} - true: + conditions: + (occupied == false): {empty: {}} + (occupied == true): string: margin: 5 text: "{id}: {state}" diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd index c1f31c6..357bae1 100644 --- a/doc/yambar-modules-script.5.scd +++ b/doc/yambar-modules-script.5.scd @@ -133,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}"}} ``` diff --git a/doc/yambar-modules.5.scd b/doc/yambar-modules.5.scd index 266d9b7..b7177c4 100644 --- a/doc/yambar-modules.5.scd +++ b/doc/yambar-modules.5.scd @@ -68,20 +68,17 @@ in red. ``` content: map: - tag: carrier - values: - false: {empty: {}} - true: + conditions: + (carrier == false): {empty: {}} + (carrier == true): 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 diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index 312fb99..f500644 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -208,7 +208,37 @@ 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 the form of: + + + +Where is the tag you would like to map, is one of: + +[- == +:- != +:- >= +:- > +:- <= +:- < + +and is the value you would like to compare it to. Conditions +may be chained together using either '&&' or '||': + + && + +You may surround the *whole expression* with parenthesis to make it +more readable: + +( && ) + +Note that "nested" conditions are *NOT* supported. That is, something +like ( && ( || )) will *NOT* work. + +Furthermore, *conditions are evaluated with a strcmp*. This means +some odd behaviour may arise if prefixes (such as zeroes) are added +to numerical constants. + +In addition to explicit tag values, you can also specify a default/fallback particle. ## CONFIGURATION @@ -217,34 +247,29 @@ default/fallback particle. :[ *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: + # Note, below, how the parenthesis are optional + 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 diff --git a/particles/map.c b/particles/map.c index abf76a6..a0f0903 100644 --- a/particles/map.c +++ b/particles/map.c @@ -1,5 +1,6 @@ #include #include +#include #include #define LOG_MODULE "map" @@ -10,13 +11,218 @@ #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 = value != NULL ? strdup(trim(value)) : NULL; + + 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 +298,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 +341,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 +370,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 +485,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,11 +500,10 @@ 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, @@ -220,28 +512,25 @@ from_conf(const struct yml_node *node, struct particle *common) }; 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, }; diff --git a/tag.c b/tag.c index 66b7f02..5c5684b 100644 --- a/tag.c +++ b/tag.c @@ -32,6 +32,30 @@ tag_name(const struct tag *tag) return priv->name; } +static enum tag_type +bool_type(const struct tag *tag) +{ + return TAG_TYPE_BOOL; +} + +static enum tag_type +int_type(const struct tag *tag) +{ + return TAG_TYPE_INT; +} + +static enum tag_type +float_type(const struct tag *tag) +{ + return TAG_TYPE_FLOAT; +} + +static enum tag_type +string_type(const struct tag *tag) +{ + return TAG_TYPE_STRING; +} + static long unimpl_min_max(const struct tag *tag) { @@ -264,6 +288,7 @@ tag_new_int_realtime(struct module *owner, const char *name, long value, tag->owner = owner; tag->destroy = &destroy_int_and_float; tag->name = &tag_name; + tag->type = &int_type; tag->min = &int_min; tag->max = &int_max; tag->realtime = &int_realtime; @@ -287,6 +312,7 @@ tag_new_bool(struct module *owner, const char *name, bool value) tag->owner = owner; tag->destroy = &destroy_int_and_float; tag->name = &tag_name; + tag->type = &bool_type; tag->min = &unimpl_min_max; tag->max = &unimpl_min_max; tag->realtime = &no_realtime; @@ -310,6 +336,7 @@ tag_new_float(struct module *owner, const char *name, double value) tag->owner = owner; tag->destroy = &destroy_int_and_float; tag->name = &tag_name; + tag->type = &float_type; tag->min = &unimpl_min_max; tag->max = &unimpl_min_max; tag->realtime = &no_realtime; @@ -333,6 +360,7 @@ tag_new_string(struct module *owner, const char *name, const char *value) tag->owner = owner; tag->destroy = &destroy_string; tag->name = &tag_name; + tag->type = &string_type; tag->min = &unimpl_min_max; tag->max = &unimpl_min_max; tag->realtime = &no_realtime; diff --git a/tag.h b/tag.h index d6bfe6a..1e9742a 100644 --- a/tag.h +++ b/tag.h @@ -3,6 +3,13 @@ #include #include +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); diff --git a/test/full-conf-good.yml b/test/full-conf-good.yml index 6270487..d89d494 100644 --- a/test/full-conf-good.yml +++ b/test/full-conf-good.yml @@ -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: From 4c4a20d83575abc567fac36c1f57c8af6421cee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Gibrowski=20Fa=C3=A9?= Date: Tue, 19 Apr 2022 22:05:42 -0300 Subject: [PATCH 79/84] Updated docs to comply with new map syntax --- doc/yambar-modules-foreign-toplevel.5.scd | 4 +- doc/yambar-modules-i3.5.scd | 2 +- doc/yambar-modules-removables.5.scd | 4 +- doc/yambar-modules-river.5.scd | 4 +- doc/yambar-modules-script.5.scd | 4 +- doc/yambar-modules.5.scd | 8 +- doc/yambar-particles.5.scd | 40 +++-- examples/configurations/laptop.conf | 187 ++++++++++------------ examples/configurations/river-tags.conf | 44 +++-- 9 files changed, 138 insertions(+), 159 deletions(-) diff --git a/doc/yambar-modules-foreign-toplevel.5.scd b/doc/yambar-modules-foreign-toplevel.5.scd index 5ef8c57..94c4b84 100644 --- a/doc/yambar-modules-foreign-toplevel.5.scd +++ b/doc/yambar-modules-foreign-toplevel.5.scd @@ -68,8 +68,8 @@ bar: content: map: conditions: - (activated == false): {empty: {}} - (activated == true): + ~activated: {empty: {}} + activated: - string: {text: "{app-id}: {title}"} ``` diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd index e646183..f82d131 100644 --- a/doc/yambar-modules-i3.5.scd +++ b/doc/yambar-modules-i3.5.scd @@ -104,7 +104,7 @@ bar: map: default: {string: {text: "{name}"}} conditions: - (state == focused): {string: {text: "{name}*"}} + state == focused: {string: {text: "{name}*"}} current: { string: {text: "{application}: {title}"}} ``` diff --git a/doc/yambar-modules-removables.5.scd b/doc/yambar-modules-removables.5.scd index 5c34aef..ee7911f 100644 --- a/doc/yambar-modules-removables.5.scd +++ b/doc/yambar-modules-removables.5.scd @@ -75,11 +75,11 @@ bar: content: map: conditions: - (mounted == false): + ~mounted: string: on-click: udisksctl mount -b {device} text: "{label}" - (mounted == true): + mounted: string: on-click: udisksctl unmount -b {device} text: "{label}" diff --git a/doc/yambar-modules-river.5.scd b/doc/yambar-modules-river.5.scd index 2255dc0..7ba2441 100644 --- a/doc/yambar-modules-river.5.scd +++ b/doc/yambar-modules-river.5.scd @@ -80,8 +80,8 @@ bar: content: map: conditions: - (occupied == false): {empty: {}} - (occupied == true): + ~occupied: {empty: {}} + occupied: string: margin: 5 text: "{id}: {state}" diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd index 357bae1..d021bf5 100644 --- a/doc/yambar-modules-script.5.scd +++ b/doc/yambar-modules-script.5.scd @@ -134,8 +134,8 @@ bar: content: map: conditions: - (status == Paused): {empty: {}} - (status == Playing): + status == Paused: {empty: {}} + status == Playing: content: {string: {text: "{artist} - {title}"}} ``` diff --git a/doc/yambar-modules.5.scd b/doc/yambar-modules.5.scd index b7177c4..ef47f62 100644 --- a/doc/yambar-modules.5.scd +++ b/doc/yambar-modules.5.scd @@ -69,16 +69,16 @@ in red. content: map: conditions: - (carrier == false): {empty: {}} - (carrier == true): + ~carrier: {empty: {}} + carrier: map: default: {string: {text: , font: *awesome, foreground: ffffff66}} conditions: - (state == up): + state == up: map: default: {string: {text: , font: *awesome}} conditions: - (ipv4 == ""): {string: {text: , font: *awesome, foreground: ffffff66}} + ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}} ``` ## Use yaml anchors diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index f500644..aaf8a92 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -208,10 +208,14 @@ content: # MAP This particle maps the values of a specific tag to different -particles based on conditions. A condition takes the form of: +particles based on conditions. A condition takes either the form of: +Or, for boolean tags: + + + Where is the tag you would like to map, is one of: [- == @@ -221,22 +225,11 @@ Where is the tag you would like to map, is one of: :- <= :- < -and is the value you would like to compare it to. Conditions -may be chained together using either '&&' or '||': +and is the value you would like to compare it to. - && +For boolean tags, negation is done with a preceding '~': -You may surround the *whole expression* with parenthesis to make it -more readable: - -( && ) - -Note that "nested" conditions are *NOT* supported. That is, something -like ( && ( || )) will *NOT* work. - -Furthermore, *conditions are evaluated with a strcmp*. This means -some odd behaviour may arise if prefixes (such as zeroes) are added -to numerical constants. +~ In addition to explicit tag values, you can also specify a default/fallback particle. @@ -264,9 +257,8 @@ content: default: string: text: this is the default particle; the tag's value is now {tag_name} - # Note, below, how the parenthesis are optional conditions: - (tag == one_value): + tag == one_value: string: text: tag's value is now one_value tag == another_value: @@ -275,6 +267,20 @@ content: ``` +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 diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index 888b19e..573516c 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -46,73 +46,65 @@ 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]}} + conditions: + state == focused: {string: {<<: [*music, *focused]}} + state == unfocused: {string: {<<: *music}} + state == invisible: {string: {<<: [*music, *invisible]}} + state == urgent: {string: {<<: [*music, *urgent]}} - foreign-toplevel: content: map: - tag: activated - values: - false: {empty: {}} - true: + conditions: + ~activated: {empty: {}} + activated: - string: {text: "{app-id}", foreground: ffa0a0ff} - string: {text: ": {title}"} center: @@ -123,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: @@ -158,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: @@ -187,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: @@ -224,16 +204,14 @@ 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: @@ -266,19 +244,18 @@ bar: - string: {text: "{capacity}% {estimate}"} content: map: - tag: state - values: - unknown: + conditions: + state == unknown: <<: *discharging - discharging: + state == discharging: <<: *discharging - charging: + 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: diff --git a/examples/configurations/river-tags.conf b/examples/configurations/river-tags.conf index 94e5874..462a329 100644 --- a/examples/configurations/river-tags.conf +++ b/examples/configurations/river-tags.conf @@ -16,16 +16,15 @@ bar: - base: &river_base left-margin: 10 right-margin: 13 - tag: id default: {string: {text: , font: *hack}} - values: - 1: {string: {text: ﳐ, font: *hack}} - 2: {string: {text: , font: *hack}} - 3: {string: {text: , font: *hack}} - 4: {string: {text: , font: *hack}} - 5: {string: {text: , font: *hack}} - 10: {string: {text: "scratchpad", font: *hack}} - 11: {string: {text: "work", 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: @@ -33,28 +32,25 @@ bar: 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)))" - tag: state - values: - urgent: + conditions: + state == urgent: map: <<: *river_base deco: {background: {color: D08770ff}} - focused: + state == focused: map: <<: *river_base deco: *bg_default - visible: + state == visible: map: - tag: occupied - values: - false: {map: {<<: *river_base}} - true: {map: {<<: *river_base, deco: *bg_default}} - unfocused: + conditions: + ~occupied: {map: {<<: *river_base}} + occupied: {map: {<<: *river_base, deco: *bg_default}} + state == unfocused: map: <<: *river_base - invisible: + state == invisible: map: - tag: occupied - values: - false: {empty: {}} - true: {map: {<<: *river_base, deco: {underline: {size: 3, color: ea6962ff}}}} + conditions: + ~occupied: {empty: {}} + occupied: {map: {<<: *river_base, deco: {underline: {size: 3, color: ea6962ff}}}} From 0d878e8b5c956653cf15963d3b0bbc405aac2887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Gibrowski=20Fa=C3=A9?= Date: Thu, 21 Apr 2022 01:06:04 -0300 Subject: [PATCH 80/84] Trimming outer '"' when parsing the values. --- doc/yambar-particles.5.scd | 4 ++++ particles/map.c | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index aaf8a92..0ac7e72 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -231,6 +231,10 @@ For boolean tags, negation is done with a preceding '~': ~ +To match for empty strings, use ' "" ': + + == "" + In addition to explicit tag values, you can also specify a default/fallback particle. diff --git a/particles/map.c b/particles/map.c index a0f0903..2c610c8 100644 --- a/particles/map.c +++ b/particles/map.c @@ -203,7 +203,16 @@ map_condition_from_str(const char *str) op_str[0] = '\0'; cond->tag = strdup(trim(tag)); - cond->value = value != NULL ? strdup(trim(value)) : NULL; + + cond->value = NULL; + if (value != NULL){ + value = trim(value); + if (value[0] == '"' && value[strlen(value) - 1] == '"'){ + value[strlen(value) - 1] = '\0'; + ++value; + } + cond->value = strdup(value); + } free(str_cpy); return cond; From 82a3b2ae112cfffef7ea34b3f0664ae45ef48402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Gibrowski=20Fa=C3=A9?= Date: Thu, 21 Apr 2022 11:48:38 -0300 Subject: [PATCH 81/84] Updates CHANGELOG.md and changes map.c formatting --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ particles/map.c | 11 ++++++----- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da20255..f3d8642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,46 @@ ### Changed * 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: + + ` ` + + where `` 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 @@ -48,6 +88,8 @@ ### Security ### Contributors +* Horus + ## 1.8.0 diff --git a/particles/map.c b/particles/map.c index 2c610c8..c748bea 100644 --- a/particles/map.c +++ b/particles/map.c @@ -101,7 +101,7 @@ eval_map_condition(const struct map_condition* map_cond, const struct tag_set *t char *end; const long cond_value = strtol(map_cond->value, &end, 0); - if (errno==ERANGE) { + if (errno == ERANGE) { LOG_WARN("value %s is too large", map_cond->value); return false; } else if (*end != '\0') { @@ -117,7 +117,7 @@ eval_map_condition(const struct map_condition* map_cond, const struct tag_set *t char *end; const double cond_value = strtod(map_cond->value, &end); - if (errno==ERANGE) { + if (errno == ERANGE) { LOG_WARN("value %s is too large", map_cond->value); return false; } else if (*end != '\0') { @@ -205,10 +205,11 @@ map_condition_from_str(const char *str) cond->tag = strdup(trim(tag)); cond->value = NULL; - if (value != NULL){ + if (value != NULL) { value = trim(value); - if (value[0] == '"' && value[strlen(value) - 1] == '"'){ - value[strlen(value) - 1] = '\0'; + const size_t value_len = strlen(value); + if (value[0] == '"' && value[value_len - 1] == '"') { + value[value_len - 1] = '\0'; ++value; } cond->value = strdup(value); From 3b5845370c0b66bc0acf59497b86f2866aa24d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Gibrowski=20Fa=C3=A9?= Date: Sun, 24 Apr 2022 11:27:32 -0300 Subject: [PATCH 82/84] doc: explain that order in map conditions matter --- doc/yambar-particles.5.scd | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/yambar-particles.5.scd b/doc/yambar-particles.5.scd index 0ac7e72..dba606a 100644 --- a/doc/yambar-particles.5.scd +++ b/doc/yambar-particles.5.scd @@ -238,6 +238,25 @@ To match for empty strings, use ' "" ': 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* From 5fc092a874ab8e3b2ca5eebb91fe2202701fb9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Apr 2022 11:04:37 +0200 Subject: [PATCH 83/84] examples: dwl-tags: adapt parsing of output to recent DWL changes(?) Closes #178 --- examples/scripts/dwl-tags.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/dwl-tags.sh b/examples/scripts/dwl-tags.sh index 395a790..bc24ac7 100755 --- a/examples/scripts/dwl-tags.sh +++ b/examples/scripts/dwl-tags.sh @@ -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- )" From a3a03340699edc9c3e1a053c9632d45060a75744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 24 Apr 2022 11:06:27 +0200 Subject: [PATCH 84/84] changelog: dwl-tags updates --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d8642..67195a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,11 @@ e.g. when handling mouse wheel events ([#169][169]). * cpu: don’t error out on systems where SMT has been disabled ([#172][172]). +* examples/dwl-tags: updated parsing of `output` name ([#178][178]). [169]: https://codeberg.org/dnkl/yambar/issues/169 [172]: https://codeberg.org/dnkl/yambar/issues/172 +[178]: https://codeberg.org/dnkl/yambar/issues/178 ### Security