diff --git a/.build.yml b/.builds/alpine-x64.yml similarity index 82% rename from .build.yml rename to .builds/alpine-x64.yml index 22b6766..a5d4e9c 100644 --- a/.build.yml +++ b/.builds/alpine-x64.yml @@ -1,4 +1,4 @@ -image: alpine/edge +image: alpine/latest packages: - musl-dev - eudev-libs @@ -21,20 +21,22 @@ packages: - alsa-lib-dev - ttf-dejavu - gcovr + - python3 + - py3-pip sources: - - https://git.sr.ht/~dnkl/yambar + - https://codeberg.org/dnkl/yambar -triggers: - - action: email - condition: failure - to: daniel@ekloef.se +# triggers: +# - action: email +# condition: failure +# to: tasks: - - install-gcovr: | - python2 -m ensurepip --user --upgrade - python2 -m pip install --user --upgrade pip - python2 -m pip install --user --upgrade setuptools + - codespell: | + pip install codespell + cd yambar + ~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd - 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/.gitignore b/.gitignore index 1e67045..6630775 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /bld/ /pkg/ /src/ -/subprojects/ +/subprojects/* +!/subprojects/*.wrap diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb34bae..06df201 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: alpine:edge +image: alpine:latest stages: - info @@ -8,7 +8,6 @@ variables: GIT_SUBMODULE_STRATEGY: normal before_script: - - echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories - apk update - apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc - apk add pixman-dev freetype-dev fontconfig-dev @@ -17,10 +16,6 @@ before_script: - apk add json-c-dev libmpdclient-dev alsa-lib-dev - apk add ttf-dejavu - apk add git - - mkdir -p subprojects && cd subprojects - - git clone https://codeberg.org/dnkl/tllist.git - - git clone https://codeberg.org/dnkl/fcft.git - - cd .. versions: stage: info @@ -92,3 +87,12 @@ plugins_as_shared_modules: - meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../ - ninja -k0 - meson test --print-errorlogs + +codespell: + image: alpine:latest + stage: build + script: + - apk add python3 + - apk add py3-pip + - pip install codespell + - codespell README.md CHANGELOG.md *.c *.h doc/*.scd diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a1acc..706b824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,51 @@ ## Unreleased ### Added + +* Text shaping support. +* Support for middle and right mouse buttons, mouse wheel and trackpad + scrolling (https://codeberg.org/dnkl/yambar/issues/39). +* script: polling mode. See the new `poll-interval` option + (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). +* fcft >= 2.4.0 is now required. +* sway-xkb: non-keyboard inputs are now ignored + (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). +* battery: differentiate "Not Charging" and "Discharging" in state + tag of battery module. + (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). + + ### Deprecated ### Removed ### Fixed * Crash when merging non-dictionary anchors in the YAML configuration (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). +* Crash when a string particle contained `{}` + (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). ### Security ### Contributors +* [novakane](https://codeberg.org/novakane) +* [mz](https://codeberg.org/mz) ## 1.6.1 diff --git a/PKGBUILD b/PKGBUILD index 12e6642..66e3efe 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -15,7 +15,7 @@ depends=( 'libudev.so' 'json-c' 'libmpdclient' - 'fcft>=2.0.0') + 'fcft>=2.4.0') optdepends=('xcb-util-errors: better X error messages') source=() diff --git a/PKGBUILD.wayland-only b/PKGBUILD.wayland-only index 10a44ea..34dd8ae 100644 --- a/PKGBUILD.wayland-only +++ b/PKGBUILD.wayland-only @@ -16,7 +16,7 @@ depends=( 'libudev.so' 'json-c' 'libmpdclient' - 'fcft>=2.0.0') + 'fcft>=2.4.0') source=() pkgver() { diff --git a/README.md b/README.md index 8052784..f61d195 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Yambar +[![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg)](https://repology.org/project/yambar/versions) + + ## Index 1. [Introduction](#introduction) @@ -87,18 +90,6 @@ Available modules: ## Installation -If you have not installed [tllist](https://codeberg.org/dnkl/tllist) -and [fcft](https://codeberg.org/dnkl/fcft) as system libraries, clone -them into the `subprojects` directory: - -```sh -mkdir -p subprojects -pushd subprojects -git clone https://codeberg.org/dnkl/tllist.git -git clone https://codeberg.org/dnkl/fcft.git -popd -``` - To build, first, create a build directory, and switch to it: ```sh mkdir -p bld/release && cd bld/release diff --git a/bar/backend.h b/bar/backend.h index f2681a8..d365da6 100644 --- a/bar/backend.h +++ b/bar/backend.h @@ -10,7 +10,7 @@ struct backend { void (*loop)(struct bar *bar, void (*expose)(const struct bar *bar), void (*on_mouse)(struct bar *bar, enum mouse_event event, - int x, int y)); + enum mouse_button btn, int x, int y)); void (*commit)(const struct bar *bar); void (*refresh)(const struct bar *bar); void (*set_cursor)(struct bar *bar, const char *cursor); diff --git a/bar/bar.c b/bar/bar.c index 526f80f..9806f44 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -2,12 +2,14 @@ #include "private.h" #include +#include #include #include #include #include #include #include +#include #include @@ -149,7 +151,8 @@ set_cursor(struct bar *bar, const char *cursor) } static void -on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) +on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, + int x, int y) { struct private *bar = _bar->private; @@ -171,7 +174,7 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) - e->on_mouse(e, _bar, event, x - mx, y); + e->on_mouse(e, _bar, event, btn, x - mx, y); return; } @@ -185,7 +188,7 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) - e->on_mouse(e, _bar, event, x - mx, y); + e->on_mouse(e, _bar, event, btn, x - mx, y); return; } @@ -203,7 +206,7 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) - e->on_mouse(e, _bar, event, x - mx, y); + e->on_mouse(e, _bar, event, btn, x - mx, y); return; } @@ -213,6 +216,20 @@ on_mouse(struct bar *_bar, enum mouse_event event, int x, int y) set_cursor(_bar, "left_ptr"); } +static void +set_module_thread_name(thrd_t id, struct module *mod) +{ + char title[16]; + if (mod->description != NULL) + strncpy(title, mod->description(mod), sizeof(title)); + else + strncpy(title, "mod:", sizeof(title)); + + title[15] = '\0'; + + if (pthread_setname_np(id, title) < 0) + LOG_ERRNO("failed to set thread title"); +} static int run(struct bar *_bar) @@ -240,18 +257,21 @@ run(struct bar *_bar) mod->abort_fd = _bar->abort_fd; thrd_create(&thrd_left[i], (int (*)(void *))bar->left.mods[i]->run, mod); + set_module_thread_name(thrd_left[i], mod); } for (size_t i = 0; i < bar->center.count; i++) { struct module *mod = bar->center.mods[i]; mod->abort_fd = _bar->abort_fd; thrd_create(&thrd_center[i], (int (*)(void *))bar->center.mods[i]->run, mod); + set_module_thread_name(thrd_center[i], mod); } for (size_t i = 0; i < bar->right.count; i++) { struct module *mod = bar->right.mods[i]; mod->abort_fd = _bar->abort_fd; thrd_create(&thrd_right[i], (int (*)(void *))bar->right.mods[i]->run, mod); + set_module_thread_name(thrd_right[i], mod); } LOG_DBG("all modules started"); @@ -388,6 +408,7 @@ bar_new(const struct bar_config *config) priv->right_spacing = config->right_spacing; priv->left_margin = config->left_margin; priv->right_margin = config->right_margin; + priv->trackpad_sensitivity = config->trackpad_sensitivity; priv->border.width = config->border.width; priv->border.color = config->border.color; priv->border.left_margin = config->border.left_margin; diff --git a/bar/bar.h b/bar/bar.h index 78e2414..4e82534 100644 --- a/bar/bar.h +++ b/bar/bar.h @@ -25,6 +25,7 @@ struct bar_config { int height; int left_spacing, right_spacing; int left_margin, right_margin; + int trackpad_sensitivity; pixman_color_t background; diff --git a/bar/private.h b/bar/private.h index b5d888f..eed532b 100644 --- a/bar/private.h +++ b/bar/private.h @@ -10,6 +10,7 @@ struct private { int height; int left_spacing, right_spacing; int left_margin, right_margin; + int trackpad_sensitivity; pixman_color_t background; diff --git a/bar/wayland.c b/bar/wayland.c index f4b3cce..7e63cf0 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -6,9 +6,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -111,8 +113,12 @@ struct wayland_backend { struct buffer *next_buffer; /* Bar is rendering to this one */ struct buffer *pending_buffer; /* Finished, but not yet rendered */ + double aggregated_scroll; + bool have_discrete; + void (*bar_expose)(const struct bar *bar); - void (*bar_on_mouse)(struct bar *bar, enum mouse_event event, int x, int y); + void (*bar_on_mouse)(struct bar *bar, enum mouse_event event, + enum mouse_button btn, int x, int y); }; static void @@ -245,6 +251,8 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, struct seat *seat = data; struct wayland_backend *backend = seat->backend; + backend->have_discrete = false; + if (backend->active_seat == seat) backend->active_seat = NULL; } @@ -261,33 +269,81 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, backend->active_seat = seat; backend->bar_on_mouse( - backend->bar, ON_MOUSE_MOTION, seat->pointer.x, seat->pointer.y); + backend->bar, ON_MOUSE_MOTION, MOUSE_BTN_NONE, + seat->pointer.x, seat->pointer.y); } static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - if (state != WL_POINTER_BUTTON_STATE_PRESSED) - return; - struct seat *seat = data; struct wayland_backend *backend = seat->backend; - backend->active_seat = seat; - backend->bar_on_mouse( - backend->bar, ON_MOUSE_CLICK, seat->pointer.x, seat->pointer.y); + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + backend->active_seat = seat; + else { + enum mouse_button btn; + + switch (button) { + case BTN_LEFT: btn = MOUSE_BTN_LEFT; break; + case BTN_MIDDLE: btn = MOUSE_BTN_MIDDLE; break; + case BTN_RIGHT: btn = MOUSE_BTN_RIGHT; break; + default: + return; + } + + backend->bar_on_mouse( + backend->bar, ON_MOUSE_CLICK, btn, seat->pointer.x, seat->pointer.y); + } } static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) + return; + + struct seat *seat = data; + struct wayland_backend *backend = seat->backend; + struct private *bar = backend->bar->private; + + backend->active_seat = seat; + + if (backend->have_discrete) + return; + + const double amount = wl_fixed_to_double(value); + + if ((backend->aggregated_scroll > 0 && amount < 0) || + (backend->aggregated_scroll < 0 && amount > 0)) + { + backend->aggregated_scroll = amount; + } else + backend->aggregated_scroll += amount; + + enum mouse_button btn = backend->aggregated_scroll > 0 + ? MOUSE_BTN_WHEEL_DOWN + : MOUSE_BTN_WHEEL_UP; + + const double step = bar->trackpad_sensitivity; + const double adjust = backend->aggregated_scroll > 0 ? -step : step; + + while (fabs(backend->aggregated_scroll) >= step) { + backend->bar_on_mouse( + backend->bar, ON_MOUSE_CLICK, btn, + seat->pointer.x, seat->pointer.y); + backend->aggregated_scroll += adjust; + } } static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { + struct seat *seat = data; + struct wayland_backend *backend = seat->backend; + backend->have_discrete = false; } static void @@ -300,12 +356,36 @@ static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) + return; + + struct seat *seat = data; + struct wayland_backend *backend = seat->backend; + backend->aggregated_scroll = 0.; } static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) + return; + + struct seat *seat = data; + struct wayland_backend *backend = seat->backend; + backend->have_discrete = true; + + enum mouse_button btn = discrete > 0 + ? MOUSE_BTN_WHEEL_DOWN + : MOUSE_BTN_WHEEL_UP; + + int count = abs(discrete); + + for (int32_t i = 0; i < count; i++) { + backend->bar_on_mouse( + backend->bar, ON_MOUSE_CLICK, btn, + seat->pointer.x, seat->pointer.y); + } } static const struct wl_pointer_listener pointer_listener = { @@ -465,6 +545,7 @@ xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct monitor *mon = data; + free(mon->name); mon->name = strdup(name); } @@ -559,7 +640,7 @@ handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, wl_seat_interface.name) == 0) { - const uint32_t required = 3; + const uint32_t required = 5; if (!verify_iface_version(interface, version, required)) return; @@ -1009,11 +1090,14 @@ cleanup(struct bar *_bar) static void loop(struct bar *_bar, void (*expose)(const struct bar *bar), - void (*on_mouse)(struct bar *bar, enum mouse_event event, int x, int y)) + void (*on_mouse)(struct bar *bar, enum mouse_event event, + enum mouse_button btn, int x, int y)) { struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; + pthread_setname_np(pthread_self(), "bar(wayland)"); + backend->bar_expose = expose; backend->bar_on_mouse = on_mouse; diff --git a/bar/xcb.c b/bar/xcb.c index f7a3ee1..9c452da 100644 --- a/bar/xcb.c +++ b/bar/xcb.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -311,11 +312,14 @@ cleanup(struct bar *_bar) static void loop(struct bar *_bar, void (*expose)(const struct bar *bar), - void (*on_mouse)(struct bar *bar, enum mouse_event event, int x, int y)) + void (*on_mouse)(struct bar *bar, enum mouse_event event, + enum mouse_button btn, int x, int y)) { struct private *bar = _bar->private; struct xcb_backend *backend = bar->backend.data; + pthread_setname_np(pthread_self(), "bar(xcb)"); + const int fd = xcb_get_file_descriptor(backend->conn); while (true) { @@ -354,7 +358,7 @@ loop(struct bar *_bar, case XCB_MOTION_NOTIFY: { const xcb_motion_notify_event_t *evt = (void *)e; - on_mouse(_bar, ON_MOUSE_MOTION, evt->event_x, evt->event_y); + on_mouse(_bar, ON_MOUSE_MOTION, MOUSE_BTN_NONE, evt->event_x, evt->event_y); break; } @@ -363,7 +367,13 @@ loop(struct bar *_bar, case XCB_BUTTON_RELEASE: { const xcb_button_release_event_t *evt = (void *)e; - on_mouse(_bar, ON_MOUSE_CLICK, evt->event_x, evt->event_y); + + switch (evt->detail) { + case 1: case 2: case 3: case 4: case 5: + on_mouse(_bar, ON_MOUSE_CLICK, + evt->detail, evt->event_x, evt->event_y); + break; + } break; } diff --git a/config-verify.c b/config-verify.c index 7e87d42..6c09a4a 100644 --- a/config-verify.c +++ b/config-verify.c @@ -152,6 +152,26 @@ conf_verify_dict(keychain_t *chain, const struct yml_node *node, return true; } +bool +conf_verify_on_click(keychain_t *chain, const struct yml_node *node) +{ + /* on-click: */ + const char *s = yml_value_as_string(node); + if (s != NULL) + return true; + + static const struct attr_info info[] = { + {"left", false, &conf_verify_string}, + {"middle", false, &conf_verify_string}, + {"right", false, &conf_verify_string}, + {"wheel-up", false, &conf_verify_string}, + {"wheel-down", false, &conf_verify_string}, + {NULL, false, NULL}, + }; + + return conf_verify_dict(chain, node, info); +} + bool conf_verify_color(keychain_t *chain, const struct yml_node *node) { @@ -403,6 +423,8 @@ conf_verify_bar(const struct yml_node *bar) {"center", false, &verify_module_list}, {"right", false, &verify_module_list}, + {"trackpad-sensitivity", false, &conf_verify_int}, + {NULL, false, NULL}, }; diff --git a/config-verify.h b/config-verify.h index 44a6a66..dccaf5f 100644 --- a/config-verify.h +++ b/config-verify.h @@ -40,6 +40,7 @@ bool conf_verify_list(keychain_t *chain, const struct yml_node *node, bool conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct attr_info info[]); /* NULL-terminated list */ +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); diff --git a/config.c b/config.c index aaa62f9..bc6d7a7 100644 --- a/config.c +++ b/config.c @@ -139,8 +139,37 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited) int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0; - const char *on_click_template - = on_click != NULL ? yml_value_as_string(on_click) : NULL; + const char *on_click_templates[MOUSE_BTN_COUNT] = {NULL}; + if (on_click != NULL) { + const char *legacy = yml_value_as_string(on_click); + + if (legacy != NULL) + on_click_templates[MOUSE_BTN_LEFT] = legacy; + + if (yml_is_dict(on_click)) { + for (struct yml_dict_iter it = yml_dict_iter(on_click); + it.key != NULL; + yml_dict_next(&it)) + { + const char *key = yml_value_as_string(it.key); + const char *template = yml_value_as_string(it.value); + + if (strcmp(key, "left") == 0) + on_click_templates[MOUSE_BTN_LEFT] = template; + else if (strcmp(key, "middle") == 0) + on_click_templates[MOUSE_BTN_MIDDLE] = template; + else if (strcmp(key, "right") == 0) + on_click_templates[MOUSE_BTN_RIGHT] = template; + else if (strcmp(key, "wheel-up") == 0) + on_click_templates[MOUSE_BTN_WHEEL_UP] = template; + else if (strcmp(key, "wheel-down") == 0) + on_click_templates[MOUSE_BTN_WHEEL_DOWN] = template; + else + assert(false); + } + } + } + struct deco *deco = deco_node != NULL ? conf_to_deco(deco_node) : NULL; /* @@ -159,7 +188,7 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited) /* Instantiate base/common particle */ struct particle *common = particle_common_new( - left, right, on_click_template, font, foreground, deco); + left, right, on_click_templates, font, foreground, deco); const struct particle_iface *iface = plugin_load_particle(type); @@ -223,6 +252,12 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend) if (right_margin != NULL) conf.right_margin = yml_value_as_int(right_margin); + const struct yml_node *trackpad_sensitivity = + yml_get_value(bar, "trackpad-sensitivity"); + conf.trackpad_sensitivity = trackpad_sensitivity != NULL + ? yml_value_as_int(trackpad_sensitivity) + : 30; + const struct yml_node *border = yml_get_value(bar, "border"); if (border != NULL) { const struct yml_node *width = yml_get_value(border, "width"); diff --git a/doc/meson.build b/doc/meson.build index b4dccb3..4598ab2 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -4,8 +4,15 @@ scdoc = dependency('scdoc', native: true) scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd', - 'yambar-modules.5.scd', 'yambar-particles.5.scd', - 'yambar-tags.5.scd'] + 'yambar-modules-alsa.5.scd', 'yambar-modules-backlight.5.scd', + 'yambar-modules-battery.5.scd', 'yambar-modules-clock.5.scd', + 'yambar-modules-i3.5.scd', 'yambar-modules-label.5.scd', + 'yambar-modules-mpd.5.scd', 'yambar-modules-network.5.scd', + 'yambar-modules-removables.5.scd', 'yambar-modules-river.5.scd', + 'yambar-modules-script.5.scd', 'yambar-modules-sway-xkb.5.scd', + 'yambar-modules-sway.5.scd', 'yambar-modules-xkb.5.scd', + 'yambar-modules-xwindow.5.scd', 'yambar-modules.5.scd', + 'yambar-particles.5.scd', 'yambar-tags.5.scd'] parts = man_src.split('.') name = parts[-3] section = parts[-2] diff --git a/doc/yambar-modules-alsa.5.scd b/doc/yambar-modules-alsa.5.scd new file mode 100644 index 0000000..23f3291 --- /dev/null +++ b/doc/yambar-modules-alsa.5.scd @@ -0,0 +1,51 @@ +yambar-modules-alsa(5) + +# NAME +alsa - Monitors an alsa soundcard for volume and mute/unmute changes + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| volume +: range +: Volume level, with min and max as start and end range values +| percent +: range +: Volume level, as a percentage +| muted +: bool +: True if muted, otherwise false + + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| card +: string +: yes +: The soundcard name. *default* might work. +| mixer +: string +: yes +: Mixer channel to monitor. _Master_ might work. + +# EXAMPLES + +``` +bar: + left: + - alsa: + card: hw:PCH + mixer: Master + content: {string: {text: "{volume}"}} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-backlight.5.scd b/doc/yambar-modules-backlight.5.scd new file mode 100644 index 0000000..7c1e6c6 --- /dev/null +++ b/doc/yambar-modules-backlight.5.scd @@ -0,0 +1,47 @@ +yambar-modules-backlight(5) + +# NAME +backlight - This module reads monitor backlight status + +# DESCRIPTION +This module reads monitor backlight status from +_/sys/class/backlight_, and uses *udev* to monitor for changes. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| brightness +: range +: The current brightness level, in absolute value +| percent +: range +: The current brightness level, in percent + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| name +: string +: yes +: The backlight device's name (one of the names in */sys/class/backlight*) + +# EXAMPLES + +``` +bar: + left: + - backlight: + name: intel_backlight + content: + string: {text: "backlight: {percent}%"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-battery.5.scd b/doc/yambar-modules-battery.5.scd new file mode 100644 index 0000000..42b5e00 --- /dev/null +++ b/doc/yambar-modules-battery.5.scd @@ -0,0 +1,66 @@ +yambar-modules-battery(5) + +# NAME +battery - This module reads battery status + +# DESCRIPTION + +This module reads battery status from _/sys/class/power_supply_ and +uses *udev* to monitor for changes. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| name +: string +: Battery device name +| manufacturer +: string +: Name of the battery manufacturer +| model +: string +: Battery model name +| state +: string +: One of *full*, *not charging*, *charging*, *discharging* or *unknown* +| capacity +: range +: capacity left, in percent +| estimate +: string +: Estimated time left (to empty while discharging, or to full while + charging), formatted as HH:MM. + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| name +: string +: yes +: Battery device name (one of the names in */sys/class/power_supply*) +| poll-interval +: int +: no +: How often, in seconds, to poll for capacity changes (default=*60*). Set to `0` to disable polling (*warning*: many batteries do not support asynchronous reporting). + +# EXAMPLES + +``` +bar: + left: + - battery: + name: BAT0 + poll-interval: 30 + content: + string: {text: "BAT: {capacity}% {estimate}"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-clock.5.scd b/doc/yambar-modules-clock.5.scd new file mode 100644 index 0000000..05e18fc --- /dev/null +++ b/doc/yambar-modules-clock.5.scd @@ -0,0 +1,47 @@ +yambar-modules-clock(5) + +# NAME +clock - This module provides the current date and time + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| time +: string +: Current time, formatted using the _time-format_ attribute +| date +: string +: Current date, formatted using the _date-format_ attribute + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| time-format +: string +: no +: *strftime* formatter for the _time_ tag (default=*%H:%M*) +| date-format +: string +: no +: *strftime* formatter for the _date_ date (default=*%x*) + +# EXAMPLES + +``` +bar: + left: + - clock: + time-format: "%H:%M %Z" + content: + string: {text: "{date} {time}"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-i3.5.scd b/doc/yambar-modules-i3.5.scd new file mode 100644 index 0000000..d61ebda --- /dev/null +++ b/doc/yambar-modules-i3.5.scd @@ -0,0 +1,104 @@ +yambar-modules-i3(5) + +# NAME +i3 - This module monitors i3 and sway workspaces + +# DESCRIPTION + +Unlike other modules where the _content_ attribute is just a single +*particle*, the i3 module's _content_ is an associative array mapping +i3/sway workspace names to a particle. + +You can add an empty workspace name, *""*, as a catch-all workspace +particle. The *i3* module will fallback to this entry if it cannot +find the workspace name in the _content_ map. + +It also recognizes the special name *current*, which always represents +the currently focused workspace. On Sway, this can be used together +with the _application_ and _title_ tags to replace the X11-only +*xwindow* module. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| name +: string +: The workspace name +| visible +: bool +: True if the workspace is currently visible (on any output) +| focused +: bool +: True if the workspace is currently focused +| urgent +: bool +: True if the workspace has the urgent flag set +| state +: string +: One of *urgent*, *focused*, *unfocused* or *invisible* (note: + *unfocused* is when it is visible, but neither focused nor urgent). +| application +: string +: Name of application currently focused on this workspace (Sway only - use the *xwindow* module in i3) +| title +: string +: This workspace's focused window's title +| mode +: string +: The name of the current mode + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| content +: associative array +: yes +: Unlike other modules, _content_ is an associative array mapping + workspace names to particles. Use *""* to specify a default + fallback particle, or *current* for the currently active workspace. +| sort +: enum +: no +: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_. +| left-spacing +: int +: no +: Space, in pixels, on the left-side of each rendered workspace particle +| right-spacing +: int +: no +: Space, in pixels, on the right-side of each rendered workspace particle +| spacing +: int +: no +: Short-hand for setting both _left-spacing_ and _right-spacing_ + +# EXAMPLES + +This renders all workspace names, with an *\** indicating the +currently focused one. It also renders the currently focused +application name and window title. + +``` +bar: + left: + - i3: + content: + "": + map: + tag: state + default: {string: {text: "{name}"}} + values: + focused: {string: {text: "{name}*"}} + current: { string: {text: "{application}: {title}"}} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-label.5.scd b/doc/yambar-modules-label.5.scd new file mode 100644 index 0000000..a6516f1 --- /dev/null +++ b/doc/yambar-modules-label.5.scd @@ -0,0 +1,32 @@ +yambar-modules-label(5) + +# NAME +label - This module renders the provided _content_ particle + +# DESCRIPTION + +This module renders the provided _content_ particle, but provides no +additional data. + +# TAGS + +None + +# CONFIGURATION + +No additional attributes supported, only the generic ones (see +*GENERIC CONFIGURATION* in *yambar-modules*(5)) + +# EXAMPLES + +``` +bar: + left: + - label: + content: {string: {text: hello world}} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-mpd.5.scd b/doc/yambar-modules-mpd.5.scd new file mode 100644 index 0000000..93e776b --- /dev/null +++ b/doc/yambar-modules-mpd.5.scd @@ -0,0 +1,80 @@ +yambar-modules-mpd(5) + +# NAME +mpd - This module provides MPD status such as currently playing artist/album/song + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| state +: string +: One of *offline*, *stopped*, *paused* or *playing* +| repeat +: bool +: True if the *repeat* flag is set +| random +: bool +: True if the *random* flag is set +| consume +: bool +: True if the *consume* flag is set +| volume +: range +: Volume of MPD in percentage +| album +: string +: Currently playing album (also valid in *paused* state) +| artist +: string +: Artist of currently playing song (also valid in *paused* state) +| title +: string +: Title of currently playing song (also valid in *paused* state) +| pos +: string +: *%M:%S*-formatted string describing the song's current position + (also see _elapsed_) +| end +: string +: *%M:%S*-formatted string describing the song's total length (also + see _duration_) +| elapsed +: realtime +: Position in currently playing song, in milliseconds. Can be used + with a _progress-bar_ particle. +| duration +: int +: Length of currently playing song, in milliseconds + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| host +: string +: yes +: Hostname/IP/unix-socket to connect to +| port +: int +: no +: TCP port to connect to + +# EXAMPLES + +``` +bar: + left: + - mpd: + host: /run/mpd/socket + content: + string: {text: "{artist} - {album} - {title} ({end})"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd new file mode 100644 index 0000000..cb78809 --- /dev/null +++ b/doc/yambar-modules-network.5.scd @@ -0,0 +1,67 @@ +yambar-modules-network(5) + +# NAME +network - This module monitors network connection state + +# DESCRIPTION + +This module monitors network connection state; disconnected/connected +state and MAC/IP addresses. + +Note: while the module internally tracks all assigned IPv4/IPv6 +addresses, it currently exposes only a single IPv4 and a single IPv6 +address. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| name +: string +: Network interface name +| index +: int +: Network interface index +| carrier +: bool +: True if the interface has CARRIER. That is, if it is physically connected. +| state +: string +: One of *unknown*, *not present*, *down*, *lower layers down*, + *testing*, *dormant* or *up*. You are probably interested in *down* and *up*. +| mac +: string +: MAC address +| ipv4 +: string +: IPv4 address assigned to the interface, or *""* if none +| ipv6 +: string +: IPv6 address assigned to the interface, or *""* if none + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| name +: string +: Name of network interface to monitor + +# EXAMPLES + +``` +bar: + left: + - network: + name: wlp3s0 + content: + string: {text: "{name}: {state} ({ipv4})"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-removables.5.scd b/doc/yambar-modules-removables.5.scd new file mode 100644 index 0000000..4b984fa --- /dev/null +++ b/doc/yambar-modules-removables.5.scd @@ -0,0 +1,89 @@ +yambar-modules-removables(5) + +# NAME +removables - This module detects removable drives + +# DESCRIPTION + +This module detects removable drives (USB sticks, CD-ROMs) and +instantiates the provided _content_ particle for each detected drive. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| vendor +: string +: Name of the drive vendor +| model +: string +: Drive model name +| optical +: bool +: True if the drive is an optical drive (CD-ROM, DVD-ROM etc) +| device +: string +: Volume device name (typically */dev/sd?*) +| size +: range +: The volume's size, in bytes. The tag's maximum value is set to the + underlying block device's size +| label +: string +: The volume's label, or its size if it has no label +| mounted +: bool +: True if the volume is mounted +| mount_point +: string +: Path where the volume is mounted, or *""* if it is not mounted + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| left-spacing +: int +: no +: Space, in pixels, in the left side of each rendered volume +| right-spacing +: int +: no +: Space, in pixels, on the right side of each rendered volume +| spacing +: int +: no +: Short-hand for setting both _left-spacing_ and _right-spacing_ +| ignore +: list of strings +: no +: List of device paths that should be ignored (e.g. /dev/mmcblk0, or /dev/mmcblk0p1) + +# EXAMPLES + +``` +bar: + right: + - removables: + content: + map: + tag: mounted + values: + false: + string: + on-click: udisksctl mount -b {device} + text: "{label}" + true: + string: + on-click: udisksctl unmount -b {device} + text: "{label}" + deco: {underline: {size: 2, color: ffffffff}} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-river.5.scd b/doc/yambar-modules-river.5.scd new file mode 100644 index 0000000..46607a2 --- /dev/null +++ b/doc/yambar-modules-river.5.scd @@ -0,0 +1,85 @@ +yambar-modules-river(5) + +# NAME +river - This module provide information about the river tags + +# DESCRIPTION + +This module uses river's (https://github.com/ifreund/river, a dynamic +tiling Wayland compositor) status protocol to provide information +about the river tags. + +It has an interface similar to the i3/sway module. + +The configuration for the river module specifies one _title_ particle, +which will be instantiated with tags representing the currently active +seat and the currently focused view's title. + +It also specifies a _content_ template particle, which is instantiated +once for all 32 river tags. This means you probably want to use a +*map* particle to hide unused river tags. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| id +: int +: River tag number +| visible +: bool +: True if the river tag is focused by at least one output (i.e. visible on at least one monitor). +| focused +: bool +: True if the river tag is _visible_ and has keyboard focus. +| occupied +: bool +: True if the river tag has views (i.e. windows). +| state +: string +: Set to *focused* if _focused_ is true, *unfocused* if _visible_ is true, but _focused_ is false, or *invisible* if the river tag is not visible on any monitors. +| seat +: string +: The name of the currently active seat (*title* particle only, see CONFIGURATION) +| title +: string +: The focused view's title (*title* particle only, see CONFIGURATION) + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| title +: particle +: no +: Particle that will be instantiated with the _seat_ and _title_ tags. +| content +: particle +: yes +: Template particle that will be instantiated once for all of the 32 river tags. + +# EXAMPLES + +``` +bar: + left: + - river: + title: {string: { text: "{seat} - {title}" }} + content: + map: + tag: occupied + values: + false: {empty: {}} + true: + string: + margin: 5 + text: "{id}: {state}" +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd new file mode 100644 index 0000000..26b210f --- /dev/null +++ b/doc/yambar-modules-script.5.scd @@ -0,0 +1,119 @@ +yambar-modules-script(5) + +# NAME +script - This module executes a user-provided script (or binary!) + +# DESCRIPTION + +This module executes a user-provided script (or binary!) that writes +tags on its stdout. + +Scripts can be run in two modes: yambar polled, or continuously. In the +yambar polled mode, the script is expected to write one set of tags +and then exit. Yambar will execute the script again after a +configurable amount of time. + +In continuous mode, the script is executed once. It will typically run +in a loop, sending an updated tag set whenever it needs, or wants +to. The last tag set is used (displayed) by yambar until a new tag set +is received. This mode is intended to be used by scripts that depends +on non-polling methods to update their state. + +Tag sets, or _transactions_, are separated by an empty line +(e.g. *echo ""*). The empty line is required to commit (update) the +tag even for only one transaction. + +Each _tag_ is a single line on the format: + +``` +name|type|value +``` + +Where _name_ is what you also use to refer to the tag in the yambar +configuration, _type_ is one of the tag types defined in +*yambar-tags*(5), and _value_ is the tag’s value. + +Example: + +``` +var1|string|hello +var2|int|13 + +var1|string|world +var2|int|37 + +``` + +The example above consists of two transactions. Each transaction has +two tags: one string tag and one integer tag. The second transaction +replaces the tags from the first transaction. Note that **both** +transactions need to be terminated with an empty line. + +Supported _types_ are: + +- string +- int +- bool +- float +- range:n-m (e.g. *var|range:0-100|57*) + +# TAGS + +User defined. + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| path +: string +: yes +: Path to script/binary to execute. Must be an absolute path. +| args +: list of strings +: no +: Arguments to pass to the script/binary. +| poll-interval +: integer +: Number of seconds between each script run. If unset, continuous mode + is used. + +# EXAMPLES + +Here is an "hello world" example script: + +``` +#!/bin/sh + +while true; do + echo "test|string|hello" + echo "" + sleep 3 + + echo "test|string|world" + echo "" + sleep 3 +done +``` + +This script runs in continuous mode, and will emit a single string tag, +_test_, and alternate its value between *hello* and *world* every +three seconds. + +A corresponding yambar configuration could look like this: + +``` +bar: + left: + - script: + path: /path/to/script.sh + args: [] + content: {string: {text: "{test}"}} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-sway-xkb.5.scd b/doc/yambar-modules-sway-xkb.5.scd new file mode 100644 index 0000000..567f11a --- /dev/null +++ b/doc/yambar-modules-sway-xkb.5.scd @@ -0,0 +1,70 @@ +yambar-modules-sway-xkb(5) + +# NAME +sway-xkb - This module monitor input devices' active XKB layout + +# DESCRIPTION + +This module uses *Sway* extensions to the I3 IPC API to monitor input +devices' active XKB layout. As such, it requires Sway to be running. + +*Note* that the _content_ configuration option is a *template*; +*sway-xkb* will instantiate a particle list, where each item is +instantiated from this template, and represents an input device. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| id +: string +: Input device identifier +| layout +: string +: The input device's currently active XKB layout + +# CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| identifiers +: list of strings +: yes +: Identifiers of input devices to monitor. Use _swaymsg -t get_inputs_ to see available devices. +| content +: particle +: yes +: A particle template; each existing input device will be instantiated with this template. +| left-spacing +: int +: no +: Space, in pixels, in the left side of each rendered input device +| right-spacing +: int +: no +: Space, in pixels, on the right side of each rendered input device +| spacing +: int +: no +: Short-hand for setting both _left-spacing_ and _right-spacing_ + +# EXAMPLES + +``` +bar: + left: + - sway-xkb: + identifiers: + - 1523:7:HID_05f3:0007 + - 7247:2:USB_USB_Keykoard + spacing: 5 + content: {string: {text: "{id}: {layout}"}} +``` + +# SEE ALSO + +*yambar-modules-xkb*(5), *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-sway.5.scd b/doc/yambar-modules-sway.5.scd new file mode 100644 index 0000000..c440322 --- /dev/null +++ b/doc/yambar-modules-sway.5.scd @@ -0,0 +1,10 @@ +yambar-modules-sway(5) + +# DESCRIPTION + +Please use the i3 (*yambar-modules-i3*(5)) module, as it is fully compatible with Sway + +# SEE ALSO + +*yambar-modules*(5), *yambar-modules-i3*(5) + diff --git a/doc/yambar-modules-xkb.5.scd b/doc/yambar-modules-xkb.5.scd new file mode 100644 index 0000000..cb9b81c --- /dev/null +++ b/doc/yambar-modules-xkb.5.scd @@ -0,0 +1,52 @@ +yambar-modules-xkb(5) + +# NAME +xkb - This module monitors the currently active XKB keyboard layout + +# DESCRIPTION + +This module monitors the currently active XKB keyboard layout and +lock-key states. + +Note: this module is X11 only. It does not work in Wayland. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| name +: string +: Name of currently selected layout, long version (e.g. "English (US)") +| symbol +: string +: Name of currently selected layout, short version (e.g. "us") +| caps_lock +: bool +: True if *CapsLock* is enabled +| num_lock +: bool +: True if *NumLock* is enabled +| scroll_lock +: bool +: True if *ScrollLock* is enabled + +# CONFIGURATION + +No additional attributes supported, only the generic ones (see +*GENERIC CONFIGURATION* in *yambar-modules*(5)) + +# EXAMPLES + +``` +bar: + left: + - xkb: + content: + string: {text: "{symbol}"} +``` + +# SEE ALSO + +*yambar-modules-sway-xkb*(5), *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules-xwindow.5.scd b/doc/yambar-modules-xwindow.5.scd new file mode 100644 index 0000000..b4c8e66 --- /dev/null +++ b/doc/yambar-modules-xwindow.5.scd @@ -0,0 +1,45 @@ +yambar-modules-xwindow(5) + +# NAME +xwindow - This module provides the application name and window title + +# DESCRIPTION + +This module provides the application name and window title of the +currently focused window. + +Note: this module is X11 only. It does not work in Wayland. If you are +running Sway, take a look at the *i3* module and its _application_ and +_title_ tags. + +# TAGS + +[[ *Name* +:[ *Type* +:[ *Description* +| application +: string +: Name of the application that owns the currently focused window +| title +: string +: The title of the currently focused window + +# CONFIGURATION + +No additional attributes supported, only the generic ones (see +*GENERIC CONFIGURATION* in *yambar-modules*(5)) + +# EXAMPLES + +``` +bar: + left: + - xwindow: + content: + string: {text: "{application}: {title}"} +``` + +# SEE ALSO + +*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar-modules.5.scd b/doc/yambar-modules.5.scd index 14aed8f..266d9b7 100644 --- a/doc/yambar-modules.5.scd +++ b/doc/yambar-modules.5.scd @@ -133,835 +133,41 @@ following attributes are supported by all modules: : Foreground (text) color of the content particle. This is an inherited attribute. -# ALSA +# BUILT-IN MODULES -Monitors an alsa soundcard for volume and mute/unmute changes. +Available modules have their own pages: -## TAGS +*yambar-modules-alsa*(5) -[[ *Name* -:[ *Type* -:[ *Description* -| volume -: range -: Volume level, with min and max as start and end range values -| percent -: range -: Volume level, as a percentage -| muted -: bool -: True if muted, otherwise false +*yambar-modules-backlight*(5) +*yambar-modules-battery*(5) -## CONFIGURATION +*yambar-modules-clock*(5) -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| card -: string -: yes -: The soundcard name. _Default_ might work. -| mixer -: string -: yes -: Mixer channel to monitor. _Master_ might work. +*yambar-modules-i3*(5) -## EXAMPLES +*yambar-modules-label*(5) -``` -bar: - left: - - alsa: - card: hw:PCH - mixer: Master - content: {string: {text: "{volume}"}} -``` +*yambar-modules-mpd*(5) -# BACKLIGHT +*yambar-modules-network*(5) -This module reads monitor backlight status from -_/sys/class/backlight_, and uses *udev* to monitor for changes. +*yambar-modules-removables*(5) -## TAGS +*yambar-modules-river*(5) -[[ *Name* -:[ *Type* -:[ *Description* -| brightness -: range -: The current brightness level, in absolute value -| percent -: range -: The current brightness level, in percent +*yambar-modules-script*(5) -## CONFIGURATION +*yambar-modules-sway-xkb*(5) -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| name -: string -: yes -: The backlight device's name (one of the names in */sys/class/backlight*) +*yambar-modules-sway*(5) -## EXAMPLES +*yambar-modules-xkb*(5) -``` -bar: - left: - - backlight: - name: intel_backlight - content: - string: {text: "backlight: {percent}%"} -``` - -# BATTERY - -This module reads battery status from _/sys/class/power_supply_ and -uses *udev* to monitor for changes. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| name -: string -: Battery device name -| manufacturer -: string -: Name of the battery manufacturer -| model -: string -: Battery model name -| state -: string -: One of *full*, *charging*, *discharging* or *unknown* -| capacity -: range -: capacity left, in percent -| estimate -: string -: Estimated time left (to empty while discharging, or to full while - charging), formatted as HH:MM. - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| name -: string -: yes -: Battery device name (one of the names in */sys/class/power_supply*) -| poll-interval -: int -: no -: How often, in seconds, to poll for capacity changes (default=*60*). Set to `0` to disable polling (*warning*: many batteries do not support asynchronous reporting). - -## EXAMPLES - -``` -bar: - left: - - battery: - name: BAT0 - poll-interval: 30 - content: - string: {text: "BAT: {capacity}% {estimate}"} -``` - -# CLOCK - -This module provides the current date and time. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| time -: string -: Current time, formatted using the _time-format_ attribute -| date -: string -: Current date, formatted using the _date-format_ attribute - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| time-format -: string -: no -: *strftime* formatter for the _time_ tag (default=*%H:%M*) -| date-format -: string -: no -: *strftime* formatter for the _date_ date (default=*%x*) - -## EXAMPLES - -``` -bar: - left: - - clock: - time-format: "%H:%M %Z" - content: - string: {text: "{date} {time}"} -``` - -# I3 (and Sway) - -This module monitors i3 and sway workspaces. - -Unlike other modules where the _content_ attribute is just a single -*particle*, the i3 module's _content_ is an associative array mapping -i3/sway workspace names to a particle. - -You can add an empty workspace name, *""*, as a catch-all workspace -particle. The *i3* module will fallback to this entry if it cannot -find the workspace name in the _content_ map. - -It also recognizes the special name *current*, which always represents -the currently focused workspace. On Sway, this can be used together -with the _application_ and _title_ tags to replace the X11-only -*xwindow* module. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| name -: string -: The workspace name -| visible -: bool -: True if the workspace is currently visible (on any output) -| focused -: bool -: True if the workspace is currently focused -| urgent -: bool -: True if the workspace has the urgent flag set -| state -: string -: One of *urgent*, *focused*, *unfocused* or *invisible* (note: - *unfocused* is when it is visible, but neither focused nor urgent). -| application -: string -: Name of application currently focused on this workspace (Sway only - use the *xwindow* module in i3) -| title -: string -: This workspace's focused window's title -| mode -: string -: The name of the current mode - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| content -: associative array -: yes -: Unlike other modules, _content_ is an associative array mapping - workspace names to particles. Use *""* to specify a default - fallback particle, or *current* for the currently active workspace. -| sort -: enum -: no -: How to sort the list of workspaces; one of _none_, _ascending_ or _descending_, defaults to _none_. -| left-spacing -: int -: no -: Space, in pixels, on the left-side of each rendered workspace particle -| right-spacing -: int -: no -: Space, in pixels, on the right-side of each rendered workspace particle -| spacing -: int -: no -: Short-hand for setting both _left-spacing_ and _right-spacing_ - -## EXAMPLES - -This renders all workspace names, with an *\** indicating the -currently focused one. It also renders the currently focused -application name and window title. - -``` -bar: - left: - - i3: - content: - "": - map: - tag: state - default: {string: {text: "{name}"}} - values: - focused: {string: {text: "{name}*"}} - current: { string: {text: "{application}: {title}"}} -``` - -# LABEL - -This module renders the provided _content_ particle, but provides no -additional data. - -## TAGS - -None - -## CONFIGURATION - -No additional attributes supported, only the generic ones (see -*GENERIC CONFIGURATION*) - -## EXAMPLES - -``` -bar: - left: - - label: - content: {string: {text: hello world}} -``` - -# MPD - -This module provides MPD status such as currently playing -artist/album/song. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| state -: string -: One of *offline*, *stopped*, *paused* or *playing* -| repeat -: bool -: True if the *repeat* flag is set -| random -: bool -: True if the *random* flag is set -| consume -: bool -: True if the *consume* flag is set -| volume -: range -: Volume of MPD in percentage -| album -: string -: Currently playing album (also valid in *paused* state) -| artist -: string -: Artist of currently playing song (also valid in *paused* state) -| title -: string -: Title of currently playing song (also valid in *paused* state) -| pos -: string -: *%M:%S*-formatted string describing the song's current position - (also see _elapsed_) -| end -: string -: *%M:%S*-formatted string describing the song's total length (also - see _duration_) -| elapsed -: realtime -: Position in currently playing song, in milliseconds. Can be used - with a _progress-bar_ particle. -| duration -: int -: Length of currently playing song, in milliseconds - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| host -: string -: yes -: Hostname/IP/unix-socket to connect to -| port -: int -: no -: TCP port to connect to - -## EXAMPLES - -``` -bar: - left: - - mpd: - host: /run/mpd/socket - content: - string: {text: "{artist} - {album} - {title} ({end})"} -``` - -# NETWORK - -This module monitors network connection state; disconnected/connected -state and MAC/IP addresses. - -Note: while the module internally tracks all assigned IPv4/IPv6 -addresses, it currently exposes only a single IPv4 and a single IPv6 -address. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| name -: string -: Network interface name -| index -: int -: Network interface index -| carrier -: bool -: True if the interface has CARRIER. That is, if it is physically connected. -| state -: string -: One of *unknown*, *not present*, *down*, *lower layers down*, - *testing*, *dormant* or *up*. You are probably interested in *down* and *up*. -| mac -: string -: MAC address -| ipv4 -: string -: IPv4 address assigned to the interface, or *""* if none -| ipv6 -: string -: IPv6 address assigned to the interface, or *""* if none - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| name -: string -: Name of network interface to monitor - -## EXAMPLES - -``` -bar: - left: - - network: - name: wlp3s0 - content: - string: {text: "{name}: {state} ({ipv4})"} -``` - -# REMOVABLES - -This module detects removable drives (USB sticks, CD-ROMs) and -instantiates the provided _content_ particle for each detected drive. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| vendor -: string -: Name of the drive vendor -| model -: string -: Drive model name -| optical -: bool -: True if the drive is an optical drive (CD-ROM, DVD-ROM etc) -| device -: string -: Volume device name (typically */dev/sd?*) -| size -: range -: The volume's size, in bytes. The tag's maximum value is set to the - underlying block device's size -| label -: string -: The volume's label, or its size if it has no label -| mounted -: bool -: True if the volume is mounted -| mount_point -: string -: Path where the volume is mounted, or *""* if it is not mounted - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| left-spacing -: int -: no -: Space, in pixels, in the left side of each rendered volume -| right-spacing -: int -: no -: Space, in pixels, on the right side of each rendered volume -| spacing -: int -: no -: Short-hand for setting both _left-spacing_ and _right-spacing_ -| ignore -: list of strings -: no -: List of device paths that should be ignored (e.g. /dev/mmcblk0, or /dev/mmcblk0p1) - -## EXAMPLES - -``` -bar: - right: - - removables: - content: - map: - tag: mounted - values: - false: - string: - on-click: udisksctl mount -b {device} - text: "{label}" - true: - string: - on-click: udisksctl unmount -b {device} - text: "{label}" - deco: {underline: {size: 2, color: ffffffff}} -``` - -# RIVER - -This module uses river's (https://github.com/ifreund/river, a dynamic -tiling Wayland compositor) status protocol to provide information -about the river tags. - -It has an interface similar to the i3/sway module. - -The configuration for the river module specifies one _title_ particle, -which will be instantiated with tags representing the currently active -seat and the currently focused view's title. - -It also specifies a _content_ template particle, which is instantiated -once for all 32 river tags. This means you probably want to use a -*map* particle to hide unused river tags. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| id -: int -: River tag number -| visible -: bool -: True if the river tag is focused by at least one output (i.e. visible on at least one monitor). -| focused -: bool -: True if the river tag is _visible_ and has keyboard focus. -| occupied -: bool -: True if the river tag has views (i.e. windows). -| state -: string -: Set to *focused* if _focused_ is true, *unfocused* if _visible_ is true, but _focused_ is false, or *invisible* if the river tag is not visible on any monitors. -| seat -: string -: The name of the currently active seat (*title* particle only, see CONFIGURATION) -| title -: string -: The focused view's title (*title* particle only, see CONFIGURATION) - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| title -: particle -: no -: Particle that will be instantiated with the _seat_ and _title_ tags. -| content -: particle -: yes -: Template particle that will be instantiated once for all of the 32 river tags. - -## EXAMPLES - -``` -bar: - left: - - river: - title: {string: { text: "{seat} - {title}" }} - content: - map: - tag: occupied - values: - false: {empty: {}} - true: - string: - margin: 5 - text: "{id}: {state}" -``` - -# SCRIPT - -This module executes a user-provided script (or binary!) that writes -tags on its stdout. - -The script can either exit immediately after writing a set of tags, in -which case yambar will display those tags until yambar is -terminated. Or, the script can continue executing and update yambar -with new tag sets, either periodically, or when there is new data to -feed to yambar. - -Tag sets, or _transactions_, are separated by an empty line. Each -_tag_ is a single line on the format: - -``` -name|type|value -``` - -Where _name_ is what you also use to refer to the tag in the yambar -configuration, _type_ is one of the tag types defined in -*yambar-tags*(5), and _value_ is the tag’s value. - -Example: - -``` -var1|string|hello -var2|int|13 - -var1|string|world -var2|int|37 -``` - -The example above consists of two transactions. Each transaction has -two tags: one string tag and one integer tag. The second transaction -replaces the tags from the first transaction. - -Supported _types_ are: - -- string -- int -- bool -- float -- range:n-m (e.g. *var|range:0-100|57*) - -## TAGS - -User defined. - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| path -: string -: yes -: Path to script/binary to execute. Must be an absolute path. -| args -: list of strings -: no -: Arguments to pass to the script/binary. - -## EXAMPLES - -Here is an "hello world" example script: - -``` -#!/bin/sh - -while true; do - echo "test|string|hello" - echo "" - sleep 3 - - echo "test|string|world" - echo "" - sleep 3 -done -``` - -This script will emit a single string tag, _test_, and alternate its -value between *hello* and *world* every three seconds. - -A corresponding yambar configuration could look like this: - -``` -bar: - left: - - script: - path: /path/to/script.sh - args: [] - content: {string: {text: "{test}"}} -``` - -# SWAY-XKB - -This module uses *Sway* extensions to the I3 IPC API to monitor input -devices' active XKB layout. As such, it requires Sway to be running. - -*Note* that the _content_ configuration option is a *template*; -*sway-xkb* will instantiate a particle list, where each item is -instantiated from this template, and represents an input device. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| id -: string -: Input device identifier -| layout -: string -: The input device's currently active XKB layout - -## CONFIGURATION - -[[ *Name* -:[ *Type* -:[ *Req* -:[ *Description* -| identifiers -: list of strings -: yes -: Identifiers of input devices to monitor. Use _swaymsg -t get_inputs_ to see available devices. -| content -: particle -: yes -: A particle template; each existing input device will be instantiated with this template. -| left-spacing -: int -: no -: Space, in pixels, in the left side of each rendered input device -| right-spacing -: int -: no -: Space, in pixels, on the right side of each rendered input device -| spacing -: int -: no -: Short-hand for setting both _left-spacing_ and _right-spacing_ - -## EXAMPLES - -``` -bar: - left: - - sway-xkb: - identifiers: - - 1523:7:HID_05f3:0007 - - 7247:2:USB_USB_Keykoard - spacing: 5 - content: {string: {text: "{id}: {layout}"}} -``` - -# XKB - -This module monitors the currently active XKB keyboard layout and -lock-key states. - -Note: this module is X11 only. It does not work in Wayland. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| name -: string -: Name of currently selected layout, long version (e.g. "English (US)") -| symbol -: string -: Name of currently selected layout, short version (e.g. "us") -| caps_lock -: bool -: True if *CapsLock* is enabled -| num_lock -: bool -: True if *NumLock* is enabled -| scroll_lock -: bool -: True if *ScrollLock* is enabled - -## CONFIGURATION - -No additional attributes supported, only the generic ones (see -*GENERIC CONFIGURATION*) - -## EXAMPLES - -``` -bar: - left: - - xkb: - content: - string: {text: "{symbol}"} -``` - -# XWINDOW - -This module provides the application name and window title of the -currently focused window. - -Note: this module is X11 only. It does not work in Wayland. If you are -running Sway, take a look at the *i3* module and its _application_ and -_title_ tags. - -## TAGS - -[[ *Name* -:[ *Type* -:[ *Description* -| application -: string -: Name of the application that owns the currently focused window -| title -: string -: The title of the currently focused window - -## CONFIGURATION - -No additional attributes supported, only the generic ones (see -*GENERIC CONFIGURATION*) - -## EXAMPLES - -``` -bar: - left: - - xwindow: - content: - string: {text: "{application}: {title}"} -``` +*yambar-modules-xwindow*(5) # SEE ALSO *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) + diff --git a/doc/yambar.5.scd b/doc/yambar.5.scd index 6b4a5ff..d352e0e 100644 --- a/doc/yambar.5.scd +++ b/doc/yambar.5.scd @@ -109,6 +109,12 @@ types that are frequently used: : color : no : Default foreground (text) color to use +| trackpad-sensitivity +: int +: no +: How easy it is to trigger wheel-up and wheel-down on-click + handlers. Higher values means you need to drag your finger a longer + distance. The default is 30. | left : list : no diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index d124e72..593d9f3 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -45,6 +45,16 @@ bar: - urgent: &urgent 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: {}} content: "": map: @@ -100,11 +110,14 @@ bar: left-margin: 7 tag: application values: - "": {string: {text: "{title}"}} + "": + - 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} @@ -258,6 +271,21 @@ bar: full: - string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}% full"} + not charging: + - ramp: + tag: capacity + items: + - string: {text:  , foreground: ff0000ff, font: *awesome} + - string: {text:  , foreground: ffa600ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text:  , foreground: 00ff00ff, font: *awesome} + - string: {text: "{capacity}%"} - clock: time-format: "%H:%M %Z" content: diff --git a/examples/scripts/dwl-tags.sh b/examples/scripts/dwl-tags.sh new file mode 100755 index 0000000..395a790 --- /dev/null +++ b/examples/scripts/dwl-tags.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# +# dwl-tags.sh - display dwl tags +# +# USAGE: dwl-tags.sh 1 +# +# REQUIREMENTS: +# - inotifywait ( 'inotify-tools' on arch ) +# - Launch dwl with `dwl > ~.cache/dwltags` or change $fname +# +# TAGS: +# Name Type Return +# ---------------------------------------------------- +# {tag_N} string dwl tags name +# {tag_N_occupied} bool dwl tags state occupied +# {tag_N_focused} bool dwl tags state focused +# {layout} string dwl layout +# {title} string client title +# +# Now the fun part +# +# Exemple configuration: +# +# - script: +# path: /absolute/path/to/dwl-tags.sh +# args: [1] +# anchors: +# - occupied: &occupied {foreground: 57bbf4ff} +# - focused: &focused {foreground: fc65b0ff} +# - default: &default {foreground: d2ccd6ff} +# content: +# - map: +# margin: 4 +# tag: tag_0_occupied +# values: +# true: +# map: +# tag: tag_0_focused +# values: +# true: {string: {text: "{tag_0}", <<: *focused}} +# false: {string: {text: "{tag_0}", <<: *occupied}} +# false: +# map: +# tag: tag_0_focused +# values: +# true: {string: {text: "{tag_0}", <<: *focused}} +# false: {string: {text: "{tag_0}", <<: *default}} +# ... +# ... +# ... +# - map: +# margin: 4 +# tag: tag_8_occupied +# values: +# true: +# map: +# tag: tag_8_focused +# values: +# true: {string: {text: "{tag_8}", <<: *focused}} +# false: {string: {text: "{tag_8}", <<: *occupied}} +# false: +# map: +# tag: tag_8_focused +# values: +# true: {string: {text: "{tag_8}", <<: *focused}} +# false: {string: {text: "{tag_8}", <<: *default}} +# - list: +# spacing: 3 +# items: +# - string: {text: "{layout}"} +# - string: {text: "{title}"} + + +# Variables +declare output title layout activetags selectedtags +declare -a tags name +readonly fname="$HOME"/.cache/dwltags + + +_cycle() { + tags=( "1" "2" "3" "4" "5" "6" "7" "8" "9" ) + + # Name of tag (optional) + # If there is no name, number are used + # + # Example: + # name=( "" "" "" "Media" ) + # -> return "" "" "" "Media" 5 6 7 8 9) + name=() + + for tag in "${!tags[@]}"; do + mask=$((1</dev/null; then + printf -- '%s\n' "${tag_name}_${tag}_focused|bool|true" + printf -- '%s\n' "title|string|${title}" + else + printf '%s\n' "${tag_name}_${tag}_focused|bool|false" + fi + + if (( "${activetags}" & mask )) 2>/dev/null; then + printf -- '%s\n' "${tag_name}_${tag}_occupied|bool|true" + else + printf -- '%s\n' "${tag_name}_${tag}_occupied|bool|false" + fi + done + + printf -- '%s\n' "layout|string|${layout}" + printf -- '%s\n' "" + +} + +# Call the function here so the tags are displayed at dwl launch +_cycle + +while true; do + + [[ ! -f "${fname}" ]] && printf -- '%s\n' \ + "You need to redirect dwl stdout to ~/.cache/dwltags" >&2 + + inotifywait -qq --event modify "${fname}" + + # Get info from the file + output="$(tail -n4 "${fname}")" + title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )" + #selmon="$(echo "${output}" | grep 'selmon')" + layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )" + + # Get the tag bit mask as a decimal + activetags="$(echo "${output}" | grep tags | awk '{print $3}')" + selectedtags="$(echo "${output}" | grep tags | awk '{print $4}')" + + _cycle + +done + +unset -v output title layout activetags selectedtags +unset -v tags name + diff --git a/examples/scripts/pacman.sh b/examples/scripts/pacman.sh new file mode 100755 index 0000000..a20fd6b --- /dev/null +++ b/examples/scripts/pacman.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# pacman.sh - display number of packages update available +# by default check every hour +# +# USAGE: pacman.sh +# +# TAGS: +# Name Type Return +# ------------------------------------------- +# {pacman} int number of pacman packages +# {aur} int number of aur packages +# {pkg} int sum of both +# +# Exemples configuration: +# - script: +# path: /absolute/path/to/pacman.sh +# args: [] +# content: { string: { text: "{pacman} + {aur} = {pkg}" } } +# +# To display a message when there is no update: +# - script: +# path: /absolute/path/to/pacman.sh +# args: [] +# content: +# map: +# tag: pkg +# default: { string: { text: "{pacman} + {aur} = {pkg}" } } +# values: +# 0: {string: {text: no updates}} + + +declare interval aur_helper pacman_num aur_num pkg_num + +# Error message in STDERR +_err() { + printf -- '%s\n' "[$(date +'%Y-%m-%d %H:%M:%S')]: $*" >&2 +} + +# Display tags before yambar fetch the updates number +printf -- '%s\n' "pacman|int|0" +printf -- '%s\n' "aur|int|0" +printf -- '%s\n' "pkg|int|0" +printf -- '%s\n' "" + + +while true; do + # Change interval + # NUMBER[SUFFIXE] + # Possible suffix: + # "s" seconds / "m" minutes / "h" hours / "d" days + interval="1h" + + # Change your aur manager + aur_helper="paru" + + # Get number of packages to update + pacman_num=$(checkupdates | wc -l) + + if ! hash "${aur_helper}" >/dev/null 2>&1; then + _err "aur helper not found, change it in the script" + exit 1 + else + aur_num=$("${aur_helper}" -Qmu | wc -l) + fi + + pkg_num=$(( pacman_num + aur_num )) + + printf -- '%s\n' "pacman|int|${pacman_num}" + printf -- '%s\n' "aur|int|${aur_num}" + printf -- '%s\n' "pkg|int|${pkg_num}" + printf -- '%s\n' "" + + sleep "${interval}" + +done + +unset -v interval aur_helper pacman_num aur_num pkg_num +unset -f _err + diff --git a/external/wlr-layer-shell-unstable-v1.xml b/external/wlr-layer-shell-unstable-v1.xml index fa67001..d62fd51 100644 --- a/external/wlr-layer-shell-unstable-v1.xml +++ b/external/wlr-layer-shell-unstable-v1.xml @@ -25,7 +25,7 @@ THIS SOFTWARE. - + Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and @@ -47,6 +47,12 @@ or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. @@ -94,7 +100,7 @@ - + An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like @@ -103,6 +109,14 @@ Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. @@ -189,21 +203,85 @@ + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + - Set to 1 to request that the seat send keyboard events to this layer - surface. For layers below the shell surface layer, the seat will use - normal focus semantics. For layers above the shell surface layers, the - seat will always give exclusive keyboard focus to the top-most layer - which has keyboard interactivity set to true. + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. - Events is double-buffered, see wl_surface.commit. + Keyboard interactivity is double-buffered, see wl_surface.commit. - + @@ -288,6 +366,7 @@ + diff --git a/external/wlr-protocols b/external/wlr-protocols index 16a2888..d1598e8 160000 --- a/external/wlr-protocols +++ b/external/wlr-protocols @@ -1 +1 @@ -Subproject commit 16a28885bc92869d8e589e725e7bf018432c47e4 +Subproject commit d1598e82240d6e8ca57729495a94d4e11d222033 diff --git a/main.c b/main.c index f8a8453..cab5f9e 100644 --- a/main.c +++ b/main.c @@ -273,7 +273,14 @@ main(int argc, char *const *argv) } } - log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, LOG_CLASS_WARNING); + log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, LOG_CLASS_INFO); + + _Static_assert(LOG_CLASS_ERROR + 1 == FCFT_LOG_CLASS_ERROR, + "fcft log level enum offset"); + _Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, + "fcft colorize enum mismatch"); + fcft_log_init( + (enum fcft_log_colorize)log_colorize, log_syslog, FCFT_LOG_CLASS_INFO); const struct sigaction sa = {.sa_handler = &signal_handler}; sigaction(SIGINT, &sa, NULL); diff --git a/meson.build b/meson.build index 9309064..a1e4fa1 100644 --- a/meson.build +++ b/meson.build @@ -65,7 +65,7 @@ backend_wayland = wayland_client.found() and wayland_cursor.found() # "My" dependencies, fallback to subproject tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') -fcft = dependency('fcft', version: ['>=2.0.0', '<3.0.0'], fallback: 'fcft') +fcft = dependency('fcft', version: ['>=2.4.0', '<3.0.0'], fallback: 'fcft') add_project_arguments( ['-D_GNU_SOURCE'] + @@ -100,7 +100,7 @@ version = custom_target( 'generate_version', build_always_stale: true, output: 'version.h', - command: [generate_version_sh, meson.project_version(), '@SOURCE_DIR@', '@OUTPUT@']) + command: [generate_version_sh, meson.project_version(), '@SOURCE_ROOT@', '@OUTPUT@']) yambar = executable( 'yambar', diff --git a/module.h b/module.h index b757a04..e525c87 100644 --- a/module.h +++ b/module.h @@ -26,6 +26,8 @@ struct module { /* refresh_in() should schedule a module content refresh after the * specified number of milliseconds */ bool (*refresh_in)(struct module *mod, long milli_seconds); + + const char *(*description)(struct module *mod); }; struct module *module_common_new(void); diff --git a/modules/alsa.c b/modules/alsa.c index 9e410df..3b71a25 100644 --- a/modules/alsa.c +++ b/modules/alsa.c @@ -39,6 +39,15 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + static char desc[32]; + struct private *m = mod->private; + snprintf(desc, sizeof(desc), "alsa(%s)", m->card); + return desc; +} + static struct exposable * content(struct module *mod) { @@ -287,6 +296,7 @@ alsa_new(const char *card, const char *mixer, struct particle *label) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/backlight.c b/modules/backlight.c index 053a998..e7bca2e 100644 --- a/modules/backlight.c +++ b/modules/backlight.c @@ -38,6 +38,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "backlight"; +} + static struct exposable * content(struct module *mod) { @@ -216,6 +222,7 @@ backlight_new(const char *device, struct particle *label) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/battery.c b/modules/battery.c index 7dcfc45..4a55141 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -20,7 +20,7 @@ #include "../config-verify.h" #include "../plugin.h" -enum state { STATE_FULL, STATE_CHARGING, STATE_DISCHARGING }; +enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING }; struct private { struct particle *label; @@ -57,6 +57,15 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + static char desc[32]; + struct private *m = mod->private; + snprintf(desc, sizeof(desc), "bat(%s)", m->battery); + return desc; +} + static struct exposable * content(struct module *mod) { @@ -65,6 +74,7 @@ content(struct module *mod) mtx_lock(&mod->lock); assert(m->state == STATE_FULL || + m->state == STATE_NOTCHARGING || m->state == STATE_CHARGING || m->state == STATE_DISCHARGING); @@ -79,7 +89,7 @@ content(struct module *mod) ? m->energy_full - m->energy : m->energy; double hours_as_float; - if (m->state == STATE_FULL) + if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING) hours_as_float = 0.0; else if (m->power > 0) hours_as_float = (double)energy / m->power; @@ -93,7 +103,7 @@ content(struct module *mod) ? m->charge_full - m->charge : m->charge; double hours_as_float; - if (m->state == STATE_FULL) + if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING) hours_as_float = 0.0; else if (m->current > 0) hours_as_float = (double)charge / m->current; @@ -117,6 +127,7 @@ content(struct module *mod) tag_new_string(mod, "model", m->model), tag_new_string(mod, "state", m->state == STATE_FULL ? "full" : + m->state == STATE_NOTCHARGING ? "not charging" : m->state == STATE_CHARGING ? "charging" : m->state == STATE_DISCHARGING ? "discharging" : "unknown"), @@ -349,12 +360,12 @@ update_status(struct module *mod) state = STATE_DISCHARGING; } else if (strcmp(status, "Full") == 0) state = STATE_FULL; + else if (strcmp(status, "Not charging") == 0) + state = STATE_NOTCHARGING; else if (strcmp(status, "Charging") == 0) state = STATE_CHARGING; else if (strcmp(status, "Discharging") == 0) state = STATE_DISCHARGING; - else if (strcmp(status, "Not charging") == 0) - state = STATE_DISCHARGING; else if (strcmp(status, "Unknown") == 0) state = STATE_DISCHARGING; else { @@ -434,9 +445,8 @@ run(struct module *mod) continue; } - if (!update_status(mod)) - break; - bar->refresh(bar); + if (update_status(mod)) + bar->refresh(bar); } out: @@ -461,6 +471,7 @@ battery_new(const char *battery, struct particle *label, int poll_interval_secs) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/clock.c b/modules/clock.c index e9f1694..b0db44e 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -35,6 +35,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "clock"; +} + static struct exposable * content(struct module *mod) { @@ -60,6 +66,7 @@ content(struct module *mod) return exposable; } +#include static int run(struct module *mod) { @@ -161,6 +168,7 @@ clock_new(struct particle *label, const char *date_format, const char *time_form mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/i3.c b/modules/i3.c index e2f58de..5aa3d3b 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -608,6 +608,12 @@ ws_content_for_name(struct private *m, const char *name) return NULL; } +static const char * +description(struct module *mod) +{ + return "i3/sway"; +} + static struct exposable * content(struct module *mod) { @@ -710,6 +716,7 @@ i3_new(struct i3_workspaces workspaces[], size_t workspace_count, mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/label.c b/modules/label.c index a29b6bd..01fce76 100644 --- a/modules/label.c +++ b/modules/label.c @@ -21,6 +21,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "label"; +} + static struct exposable * content(struct module *mod) { @@ -45,6 +51,7 @@ label_new(struct particle *label) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/mpd.c b/modules/mpd.c index a501f76..bebd401 100644 --- a/modules/mpd.c +++ b/modules/mpd.c @@ -83,6 +83,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "mpd"; +} + static uint64_t timespec_diff_milli_seconds(const struct timespec *a, const struct timespec *b) { @@ -588,6 +594,7 @@ mpd_new(const char *host, uint16_t port, struct particle *label) mod->destroy = &destroy; mod->content = &content; mod->refresh_in = &refresh_in; + mod->description = &description; return mod; } diff --git a/modules/network.c b/modules/network.c index 51839bf..c6b32af 100644 --- a/modules/network.c +++ b/modules/network.c @@ -66,6 +66,16 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + static char desc[32]; + struct private *m = mod->private; + + snprintf(desc, sizeof(desc), "net(%s)", m->iface); + return desc; +} + static struct exposable * content(struct module *mod) { @@ -526,6 +536,7 @@ network_new(const char *iface, struct particle *label) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/removables.c b/modules/removables.c index 498824b..4446f3d 100644 --- a/modules/removables.c +++ b/modules/removables.c @@ -97,6 +97,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "removables"; +} + static struct exposable * content(struct module *mod) { @@ -596,6 +602,7 @@ removables_new(struct particle *label, int left_spacing, int right_spacing, mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/river.c b/modules/river.c index b6e3db1..0cdab38 100644 --- a/modules/river.c +++ b/modules/river.c @@ -8,7 +8,7 @@ #include #define LOG_MODULE "river" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "../log.h" #include "../plugin.h" #include "../particles/dynlist.h" @@ -65,6 +65,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "river"; +} + static struct exposable * content(struct module *mod) { @@ -184,14 +190,15 @@ focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1 uint32_t tags) { struct output *output = data; - struct module *mod = output->m->mod; + + if (output->focused == tags) + return; LOG_DBG("output: %s: focused tags: 0x%08x", output->name, tags); + struct module *mod = output->m->mod; mtx_lock(&mod->lock); - { - output->focused = tags; - } + output->focused = tags; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } @@ -310,25 +317,25 @@ focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct private *m = seat->m; struct module *mod = m->mod; - mtx_lock(&mod->lock); - { - struct output *output = NULL; - tll_foreach(m->outputs, it) { - if (it->item.wl_output == wl_output) { - output = &it->item; - break; - } + struct output *output = NULL; + tll_foreach(m->outputs, it) { + if (it->item.wl_output == wl_output) { + output = &it->item; + break; } - - LOG_DBG("seat: %s: focused output: %s", seat->name, output != NULL ? output->name : ""); - - if (output == NULL) - LOG_WARN("seat: %s: couldn't find output we are mapped on", seat->name); - - seat->output = output; } - mtx_unlock(&mod->lock); - mod->bar->refresh(mod->bar); + + LOG_DBG("seat: %s: focused output: %s", seat->name, output != NULL ? output->name : ""); + + if (output == NULL) + LOG_WARN("seat: %s: couldn't find output we are mapped on", seat->name); + + if (seat->output != output) { + mtx_lock(&mod->lock); + seat->output = output; + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); + } } static void @@ -367,6 +374,12 @@ focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct seat *seat = data; struct module *mod = seat->m->mod; + if (seat->title == NULL && title == NULL) + return; + + if (seat->title != NULL && title != NULL && strcmp(seat->title, title) == 0) + return; + LOG_DBG("seat: %s: focused view: %s", seat->name, title); mtx_lock(&mod->lock); @@ -645,6 +658,7 @@ river_new(struct particle *template, struct particle *title) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; m->mod = mod; return mod; } diff --git a/modules/script.c b/modules/script.c index 7e07365..189f4b6 100644 --- a/modules/script.c +++ b/modules/script.c @@ -25,6 +25,8 @@ struct private { char *path; size_t argc; char **argv; + int poll_interval; + bool aborted; struct particle *content; @@ -56,6 +58,19 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + static char desc[32]; + struct private *m = mod->private; + + char *path = strdup(m->path); + snprintf(desc, sizeof(desc), "script(%s)", basename(path)); + + free(path); + return desc; +} + static struct exposable * content(struct module *mod) { @@ -180,7 +195,7 @@ process_line(struct module *mod, const char *line, size_t len) long end = 0; for (size_t i = 0; i < end_len; i++) { - if (!(_end[i] >= '0' && _end[i] < '9')) { + if (!(_end[i] >= '0' && _end[i] <= '9')) { LOG_ERR( "tag range end is not an integer: %.*s", (int)end_len, _end); @@ -318,7 +333,7 @@ data_received(struct module *mod, const char *data, size_t len) static int run_loop(struct module *mod, pid_t pid, int comm_fd) { - int ret = 0; + int ret = 1; while (true) { struct pollfd fds[] = { @@ -347,19 +362,18 @@ run_loop(struct module *mod, pid_t pid, int comm_fd) data_received(mod, data, amount); } - if (fds[0].revents & POLLHUP) { + if (fds[0].revents & (POLLHUP | POLLIN)) { /* Aborted */ + struct private *m = mod->private; + m->aborted = true; + ret = 0; break; } if (fds[1].revents & POLLHUP) { /* Child's stdout closed */ LOG_DBG("script pipe closed (script terminated?)"); - break; - } - - if (fds[0].revents & POLLIN) { - /* Aborted */ + ret = 0; break; } } @@ -368,7 +382,7 @@ run_loop(struct module *mod, pid_t pid, int comm_fd) } static int -run(struct module *mod) +execute_script(struct module *mod) { struct private *m = mod->private; @@ -552,9 +566,75 @@ run(struct module *mod) return ret; } +static int +run(struct module *mod) +{ + struct private *m = mod->private; + + int ret = 1; + bool keep_going = true; + + while (keep_going && !m->aborted) { + ret = execute_script(mod); + + if (ret != 0) + break; + if (m->aborted) + break; + if (m->poll_interval < 0) + break; + + struct timeval now; + if (gettimeofday(&now, NULL) < 0) { + LOG_ERRNO("failed to get current time"); + break; + } + + struct timeval poll_interval = {.tv_sec = m->poll_interval}; + + struct timeval timeout; + timeradd(&now, &poll_interval, &timeout); + + while (true) { + struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; + + struct timeval now; + if (gettimeofday(&now, NULL) < 0) { + LOG_ERRNO("failed to get current time"); + keep_going = false; + break; + } + + if (!timercmp(&now, &timeout, <)) { + /* We’ve reached the timeout, it’s time to execute the script again */ + break; + } + + struct timeval time_left; + timersub(&timeout, &now, &time_left); + + int r = poll(fds, 1, time_left.tv_sec * 1000 + time_left.tv_usec / 1000); + if (r < 0) { + if (errno == EINTR) + continue; + LOG_ERRNO("failed to poll"); + keep_going = false; + break; + } + + if (r > 0) { + m->aborted = true; + break; + } + } + } + + return ret; +} + static struct module * script_new(const char *path, size_t argc, const char *const argv[static argc], - struct particle *_content) + int poll_interval, struct particle *_content) { struct private *m = calloc(1, sizeof(*m)); m->path = strdup(path); @@ -563,12 +643,14 @@ script_new(const char *path, size_t argc, const char *const argv[static argc], m->argv = malloc(argc * sizeof(m->argv[0])); for (size_t i = 0; i < argc; i++) m->argv[i] = strdup(argv[i]); + m->poll_interval = poll_interval; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } @@ -578,6 +660,7 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) const struct yml_node *path = yml_get_value(node, "path"); const struct yml_node *args = yml_get_value(node, "args"); const struct yml_node *c = yml_get_value(node, "content"); + const struct yml_node *poll_interval = yml_get_value(node, "poll-interval"); size_t argc = args != NULL ? yml_list_length(args) : 0; const char *argv[argc]; @@ -593,7 +676,9 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) } return script_new( - yml_value_as_string(path), argc, argv, conf_to_particle(c, inherited)); + yml_value_as_string(path), argc, argv, + poll_interval != NULL ? yml_value_as_int(poll_interval) : -1, + conf_to_particle(c, inherited)); } static bool @@ -623,6 +708,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}, MODULE_COMMON_ATTRS, }; diff --git a/modules/sway-xkb.c b/modules/sway-xkb.c index 114a361..295b976 100644 --- a/modules/sway-xkb.c +++ b/modules/sway-xkb.c @@ -52,6 +52,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "sway-xkb"; +} + static struct exposable * content(struct module *mod) { @@ -99,6 +105,15 @@ handle_input_reply(int type, const struct json_object *json, void *_mod) return false; const char *id = json_object_get_string(identifier); + + struct json_object *type; + if (!json_object_object_get_ex(obj, "type", &type)) + return false; + if (strcmp(json_object_get_string(type), "keyboard") != 0) { + LOG_DBG("ignoring non-keyboard input '%s'", id); + continue; + } + struct input *input = NULL; for (size_t i = 0; i < m->num_inputs; i++) { struct input *maybe_input = &m->inputs[i]; @@ -166,6 +181,15 @@ handle_input_event(int type, const struct json_object *json, void *_mod) return false; const char *id = json_object_get_string(identifier); + + struct json_object *input_type; + if (!json_object_object_get_ex(obj, "type", &input_type)) + return false; + if (strcmp(json_object_get_string(input_type), "keyboard") != 0) { + LOG_DBG("ignoring non-keyboard input '%s'", id); + return true; + } + struct input *input = NULL; for (size_t i = 0; i < m->num_inputs; i++) { struct input *maybe_input = &m->inputs[i]; @@ -289,6 +313,7 @@ sway_xkb_new(struct particle *template, const char *identifiers[], mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/xkb.c b/modules/xkb.c index 16cc864..5c2c1f9 100644 --- a/modules/xkb.c +++ b/modules/xkb.c @@ -72,6 +72,12 @@ destroy(struct module *mod) module_default_destroy(mod); } +static const char * +description(struct module *mod) +{ + return "xkb"; +} + static struct exposable * content(struct module *mod) { @@ -650,6 +656,7 @@ xkb_new(struct particle *label) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/modules/xwindow.c b/modules/xwindow.c index ad856a4..3c8655e 100644 --- a/modules/xwindow.c +++ b/modules/xwindow.c @@ -36,6 +36,12 @@ struct private { xcb_window_t active_win; }; +static const char * +description(struct module *mod) +{ + return "xwindow"; +} + static void update_active_window(struct private *m) { @@ -332,6 +338,7 @@ xwindow_new(struct particle *label) mod->run = &run; mod->destroy = &destroy; mod->content = &content; + mod->description = &description; return mod; } diff --git a/particle.c b/particle.c index ffe330f..a1e2b2c 100644 --- a/particle.c +++ b/particle.c @@ -22,31 +22,41 @@ particle_default_destroy(struct particle *particle) if (particle->deco != NULL) particle->deco->destroy(particle->deco); fcft_destroy(particle->font); - free(particle->on_click_template); + for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) + free(particle->on_click_templates[i]); free(particle); } struct particle * particle_common_new(int left_margin, int right_margin, - const char *on_click_template, + const char **on_click_templates, struct fcft_font *font, 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->on_click_template = - on_click_template != NULL ? strdup(on_click_template) : NULL; p->foreground = foreground; p->font = font; p->deco = deco; + + if (on_click_templates != NULL) { + for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) { + if (on_click_templates[i] != NULL) { + p->have_on_click_template = true; + p->on_click_templates[i] = strdup(on_click_templates[i]); + } + } + } + return p; } void exposable_default_destroy(struct exposable *exposable) { - free(exposable->on_click); + for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) + free(exposable->on_click[i]); free(exposable); } @@ -141,20 +151,37 @@ err: void exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, - enum mouse_event event, int x, int y) + enum mouse_event event, enum mouse_button btn, + int x, int y) { - LOG_DBG("on_mouse: exposable=%p, event=%s, x=%d, y=%d (on-click=%s)", - exposable, event == ON_MOUSE_MOTION ? "motion" : "click", x, y, - exposable->on_click); +#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG + static const char *button_name[] = { + [MOUSE_BTN_NONE] = "none", + [MOUSE_BTN_LEFT] = "left", + [MOUSE_BTN_MIDDLE] = "middle", + [MOUSE_BTN_RIGHT] = "right", + [MOUSE_BTN_COUNT] = "count", + [MOUSE_BTN_WHEEL_UP] = "wheel-up", + [MOUSE_BTN_WHEEL_DOWN] = "wheel-down", + }; + LOG_DBG("on_mouse: exposable=%p, event=%s, btn=%s, x=%d, y=%d (on-click=%s)", + exposable, event == ON_MOUSE_MOTION ? "motion" : "click", + button_name[btn], x, y, exposable->on_click[btn]); +#endif /* If we have a handler, change cursor to a hand */ - bar->set_cursor(bar, exposable->on_click == NULL ? "left_ptr" : "hand2"); + const char *cursor = + (exposable->particle != NULL && + exposable->particle->have_on_click_template) + ? "hand2" + : "left_ptr"; + bar->set_cursor(bar, cursor); /* If this is a mouse click, and we have a handler, execute it */ - if (exposable->on_click != NULL && event == ON_MOUSE_CLICK) { + if (exposable->on_click[btn] != NULL && event == ON_MOUSE_CLICK) { /* Need a writeable copy, whose scope *we* control */ - char *cmd = strdup(exposable->on_click); - LOG_DBG("cmd = \"%s\"", exposable->on_click); + char *cmd = strdup(exposable->on_click[btn]); + LOG_DBG("cmd = \"%s\"", exposable->on_click[btn]); char **argv; if (!tokenize_cmdline(cmd, &argv)) { @@ -172,15 +199,15 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, int wstatus; if (waitpid(pid, &wstatus, 0) == -1) - LOG_ERRNO("%s: failed to wait for on_click handler", exposable->on_click); + LOG_ERRNO("%s: failed to wait for on_click handler", exposable->on_click[btn]); if (WIFEXITED(wstatus)) { if (WEXITSTATUS(wstatus) != 0) - LOG_ERRNO_P("%s: failed to execute", WEXITSTATUS(wstatus), exposable->on_click); + LOG_ERRNO_P("%s: failed to execute", WEXITSTATUS(wstatus), exposable->on_click[btn]); } else - LOG_ERR("%s: did not exit normally", exposable->on_click); + LOG_ERR("%s: did not exit normally", exposable->on_click[btn]); - LOG_DBG("%s: launched", exposable->on_click); + LOG_DBG("%s: launched", exposable->on_click[btn]); } else { /* * Use a pipe with O_CLOEXEC to communicate exec() failure @@ -250,7 +277,8 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, /* Close *all* other FDs (e.g. script modules' FDs) */ for (int i = STDERR_FILENO + 1; i < 65536; i++) - close(i); + if (i != pipe_fds[1]) + close(i); execvp(argv[0], argv); @@ -267,6 +295,8 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, int _errno = 0; ssize_t ret = read(pipe_fds[0], &_errno, sizeof(_errno)); + close(pipe_fds[0]); + if (ret == 0) { /* Pipe was closed - child succeeded with exec() */ _exit(0); @@ -281,11 +311,17 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, } struct exposable * -exposable_common_new(const struct particle *particle, const char *on_click) +exposable_common_new(const struct particle *particle, const struct tag_set *tags) { struct exposable *exposable = calloc(1, sizeof(*exposable)); exposable->particle = particle; - exposable->on_click = on_click != NULL ? strdup(on_click) : NULL; + + if (particle != NULL && particle->have_on_click_template) { + tags_expand_templates( + exposable->on_click, + (const char **)particle->on_click_templates, + MOUSE_BTN_COUNT, tags); + } exposable->destroy = &exposable_default_destroy; exposable->on_mouse = &exposable_default_on_mouse; return exposable; diff --git a/particle.h b/particle.h index e89b9c4..4ca520f 100644 --- a/particle.h +++ b/particle.h @@ -7,13 +7,31 @@ #include "decoration.h" #include "tag.h" +enum mouse_event { + ON_MOUSE_MOTION, + ON_MOUSE_CLICK, +}; + +enum mouse_button { + MOUSE_BTN_NONE, + MOUSE_BTN_LEFT, + MOUSE_BTN_MIDDLE, + MOUSE_BTN_RIGHT, + MOUSE_BTN_WHEEL_UP, + MOUSE_BTN_WHEEL_DOWN, + + MOUSE_BTN_COUNT, +}; + struct bar; struct particle { void *private; int left_margin, right_margin; - char *on_click_template; + + bool have_on_click_template; + char *on_click_templates[MOUSE_BTN_COUNT]; pixman_color_t foreground; struct fcft_font *font; @@ -24,17 +42,13 @@ struct particle { const struct tag_set *tags); }; -enum mouse_event { - ON_MOUSE_MOTION, - ON_MOUSE_CLICK, -}; struct exposable { const struct particle *particle; void *private; int width; /* Should be set by begin_expose(), at latest */ - char *on_click; + char *on_click[MOUSE_BTN_COUNT]; void (*destroy)(struct exposable *exposable); int (*begin_expose)(struct exposable *exposable); @@ -42,31 +56,31 @@ struct exposable { int x, int y, int height); void (*on_mouse)(struct exposable *exposable, struct bar *bar, - enum mouse_event event, int x, int y); + enum mouse_event event, enum mouse_button btn, int x, int y); }; struct particle *particle_common_new( - int left_margin, int right_margin, const char *on_click_template, + int left_margin, int right_margin, const char *on_click_templates[], struct fcft_font *font, pixman_color_t foreground, struct deco *deco); void particle_default_destroy(struct particle *particle); struct exposable *exposable_common_new( - const struct particle *particle, const char *on_click); + const struct particle *particle, const struct tag_set *tags); void exposable_default_destroy(struct exposable *exposable); void exposable_render_deco( const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height); void exposable_default_on_mouse( struct exposable *exposable, struct bar *bar, - enum mouse_event event, int x, int y); + 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_string}, \ + {"on-click", false, &conf_verify_on_click}, \ {"font", false, &conf_verify_font}, \ {"foreground", false, &conf_verify_color}, \ {"deco", false, &conf_verify_decoration}, \ diff --git a/particles/dynlist.c b/particles/dynlist.c index c04d610..8c6319c 100644 --- a/particles/dynlist.c +++ b/particles/dynlist.c @@ -67,13 +67,12 @@ dynlist_expose(const struct exposable *exposable, pixman_image_t *pix, int x, in static void on_mouse(struct exposable *exposable, struct bar *bar, - enum mouse_event event, int x, int y) + enum mouse_event event, enum mouse_button btn, int x, int y) { - //const struct particle *p = exposable->particle; const struct private *e = exposable->private; - if (exposable->on_click != NULL) { - exposable_default_on_mouse(exposable, bar, event, x, y); + if (exposable->on_click[btn] != NULL) { + exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } @@ -82,7 +81,7 @@ on_mouse(struct exposable *exposable, struct bar *bar, if (x >= px && x < px + e->exposables[i]->width) { if (e->exposables[i]->on_mouse != NULL) { e->exposables[i]->on_mouse( - e->exposables[i], bar, event, x - px, y); + e->exposables[i], bar, event, btn, x - px, y); } return; } @@ -91,7 +90,7 @@ on_mouse(struct exposable *exposable, struct bar *bar, } LOG_DBG("on_mouse missed all sub-particles"); - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); } struct exposable * diff --git a/particles/empty.c b/particles/empty.c index e97f929..5c0be16 100644 --- a/particles/empty.c +++ b/particles/empty.c @@ -22,13 +22,9 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { - char *on_click = tags_expand_template(particle->on_click_template, tags); - - struct exposable *exposable = exposable_common_new(particle, on_click); + struct exposable *exposable = exposable_common_new(particle, tags); exposable->begin_expose = &begin_expose; exposable->expose = &expose; - - free(on_click); return exposable; } diff --git a/particles/list.c b/particles/list.c index 6f51152..9f03231 100644 --- a/particles/list.c +++ b/particles/list.c @@ -75,14 +75,17 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int static void on_mouse(struct exposable *exposable, struct bar *bar, - enum mouse_event event, int x, int y) + enum mouse_event event, enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; - if (exposable->on_click != NULL) { + if ((event == ON_MOUSE_MOTION && + exposable->particle->have_on_click_template) || + exposable->on_click[btn] != NULL) + { /* We have our own handler */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } @@ -91,7 +94,7 @@ on_mouse(struct exposable *exposable, struct bar *bar, if (x >= px && x < px + e->exposables[i]->width) { if (e->exposables[i]->on_mouse != NULL) { e->exposables[i]->on_mouse( - e->exposables[i], bar, event, x - px, y); + e->exposables[i], bar, event, btn, x - px, y); } return; } @@ -100,7 +103,7 @@ on_mouse(struct exposable *exposable, struct bar *bar, } /* We're between sub-particles (or in the left/right margin) */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); } static struct exposable * @@ -121,16 +124,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags) assert(e->exposables[i] != NULL); } - char *on_click = tags_expand_template(particle->on_click_template, tags); - - struct exposable *exposable = exposable_common_new(particle, on_click); + struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; - - free(on_click); return exposable; } diff --git a/particles/map.c b/particles/map.c index 57bb0fb..e2d8c03 100644 --- a/particles/map.c +++ b/particles/map.c @@ -61,26 +61,29 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, - int x, int y) + enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; - if (exposable->on_click != NULL) { + if ((event == ON_MOUSE_MOTION && + exposable->particle->have_on_click_template) || + exposable->on_click[btn] != NULL) + { /* We have our own handler */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } int px = p->left_margin; if (x >= px && x < px + e->exposable->width) { if (e->exposable->on_mouse != NULL) - e->exposable->on_mouse(e->exposable, bar, event, x - px, y); + e->exposable->on_mouse(e->exposable, bar, event, btn, x - px, y); return; } /* In the left- or right margin */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); } static struct exposable * @@ -119,15 +122,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags) assert(e->exposable != NULL); - char *on_click = tags_expand_template(particle->on_click_template, tags); - struct exposable *exposable = exposable_common_new(particle, on_click); + struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; - - free(on_click); return exposable; } diff --git a/particles/progress-bar.c b/particles/progress-bar.c index ad5c4cd..a825117 100644 --- a/particles/progress-bar.c +++ b/particles/progress-bar.c @@ -85,10 +85,13 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, - int x, int y) + enum mouse_button btn, int x, int y) { - if (exposable->on_click == NULL) { - exposable_default_on_mouse(exposable, bar, event, x, y); + if ((event == ON_MOUSE_MOTION && + exposable->particle->have_on_click_template) || + exposable->on_click[btn] != NULL) + { + exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } @@ -120,7 +123,7 @@ on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, /* Mouse is over the start-marker */ struct exposable *start = e->exposables[0]; if (start->on_mouse != NULL) - start->on_mouse(start, bar, event, x - p->left_margin, y); + start->on_mouse(start, bar, event, btn, x - p->left_margin, y); } else { /* Mouse if over left margin */ bar->set_cursor(bar, "left_ptr"); @@ -139,7 +142,7 @@ on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, /* Mouse is over the end-marker */ struct exposable *end = e->exposables[e->count - 1]; if (end->on_mouse != NULL) - end->on_mouse(end, bar, event, x - x_offset - clickable_width, y); + end->on_mouse(end, bar, event, btn, x - x_offset - clickable_width, y); } else { /* Mouse is over the right margin */ bar->set_cursor(bar, "left_ptr"); @@ -148,7 +151,9 @@ on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, } /* Remember the original handler, so that we can restore it */ - char *original = exposable->on_click; + char *original[MOUSE_BTN_COUNT]; + for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) + original[i] = exposable->on_click[i]; if (event == ON_MOUSE_CLICK) { long where = clickable_width > 0 @@ -160,17 +165,21 @@ on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, .count = 1, }; - exposable->on_click = tags_expand_template(exposable->on_click, &tags); + tags_expand_templates( + exposable->on_click, (const char **)exposable->on_click, + MOUSE_BTN_COUNT, &tags); tag_set_destroy(&tags); } /* Call default implementation, which will execute our handler */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); if (event == ON_MOUSE_CLICK) { /* Reset handler string */ - free(exposable->on_click); - exposable->on_click = original; + for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) { + free(exposable->on_click[i]); + exposable->on_click[i] = original[i]; + } } } @@ -213,10 +222,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags) for (size_t i = 0; i < epriv->count; i++) assert(epriv->exposables[i] != NULL); - char *on_click = tags_expand_template(particle->on_click_template, tags); - - struct exposable *exposable = exposable_common_new(particle, on_click); - free(on_click); + struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = epriv; exposable->destroy = &exposable_destroy; diff --git a/particles/ramp.c b/particles/ramp.c index 45cc277..3fa2fc8 100644 --- a/particles/ramp.c +++ b/particles/ramp.c @@ -4,6 +4,9 @@ #include +#define LOG_MODULE "ramp" +#define LOG_ENABLE_DBG 0 +#include "../log.h" #include "../config.h" #include "../config-verify.h" #include "../particle.h" @@ -54,26 +57,29 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, - int x, int y) + enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; - if (exposable->on_click != NULL) { + if ((event == ON_MOUSE_MOTION && + exposable->particle->have_on_click_template) || + exposable->on_click[btn] != NULL) + { /* We have our own handler */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } int px = p->left_margin; if (x >= px && x < px + e->exposable->width) { if (e->exposable->on_mouse != NULL) - e->exposable->on_mouse(e->exposable, bar, event, x - px, y); + e->exposable->on_mouse(e->exposable, bar, event, btn, x - px, y); return; } /* In the left- or right margin */ - exposable_default_on_mouse(exposable, bar, event, x, y); + exposable_default_on_mouse(exposable, bar, event, btn, x, y); } static void @@ -102,6 +108,26 @@ instantiate(const struct particle *particle, const struct tag_set *tags) long min = tag != NULL ? tag->min(tag) : 0; long max = tag != NULL ? tag->max(tag) : 0; + if (min > max) { + LOG_WARN( + "tag's minimum value is greater than its maximum: " + "tag=\"%s\", min=%ld, max=%ld", p->tag, min, max); + min = max; + } + + if (value < min) { + LOG_WARN( + "tag's value is less than its minimum value: " + "tag=\"%s\", min=%ld, value=%ld", p->tag, min, value); + value = min; + } + if (value > max) { + LOG_WARN( + "tag's value is greater than its maximum value: " + "tag=\"%s\", max=%ld, value=%ld", p->tag, max, value); + value = max; + } + assert(value >= min && value <= max); assert(max >= min); @@ -123,15 +149,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags) e->exposable = pp->instantiate(pp, tags); assert(e->exposable != NULL); - char *on_click = tags_expand_template(particle->on_click_template, tags); - struct exposable *exposable = exposable_common_new(particle, on_click); + struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; - - free(on_click); return exposable; } diff --git a/particles/string.c b/particles/string.c index 5e98132..c475d5b 100644 --- a/particles/string.c +++ b/particles/string.c @@ -10,16 +10,25 @@ #include "../particle.h" #include "../plugin.h" +struct text_run_cache { + uint64_t hash; + struct fcft_text_run *run; + int width; + bool in_use; +}; + struct private { char *text; size_t max_len; + + size_t cache_size; + struct text_run_cache *cache; }; struct eprivate { - /* Set when instantiating */ - char *text; - + ssize_t cache_idx; const struct fcft_glyph **glyphs; + const struct fcft_glyph **allocated_glyphs; long *kern_x; int num_glyphs; }; @@ -29,8 +38,7 @@ exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; - free(e->text); - free(e->glyphs); + free(e->allocated_glyphs); free(e->kern_x); free(e); exposable_default_destroy(exposable); @@ -40,42 +48,19 @@ static int begin_expose(struct exposable *exposable) { struct eprivate *e = exposable->private; - struct fcft_font *font = exposable->particle->font; + struct private *p = exposable->particle->private; - e->glyphs = NULL; - e->num_glyphs = 0; - - size_t chars = mbstowcs(NULL, e->text, 0); - if (chars != (size_t)-1) { - wchar_t wtext[chars + 1]; - mbstowcs(wtext, e->text, chars + 1); - - e->glyphs = malloc(chars * sizeof(e->glyphs[0])); - e->kern_x = calloc(chars, sizeof(e->kern_x[0])); - - /* Convert text to glyph masks/images. */ - for (size_t i = 0; i < chars; i++) { - const struct fcft_glyph *glyph = fcft_glyph_rasterize( - font, wtext[i], FCFT_SUBPIXEL_NONE); - - if (glyph == NULL) - continue; - - e->glyphs[e->num_glyphs++] = glyph; - - if (i == 0) - continue; - - fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL); - } - } - - exposable->width = exposable->particle->left_margin + + exposable->width = + exposable->particle->left_margin + exposable->particle->right_margin; - /* Calculate the size we need to render the glyphs */ - for (int i = 0; i < e->num_glyphs; i++) - exposable->width += e->kern_x[i] + e->glyphs[i]->advance.x; + if (e->cache_idx >= 0) { + exposable->width += p->cache[e->cache_idx].width; + } else { + /* Calculate the size we need to render the glyphs */ + for (int i = 0; i < e->num_glyphs; i++) + exposable->width += e->kern_x[i] + e->glyphs[i]->advance.x; + } return exposable->width; } @@ -88,6 +73,11 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int const struct eprivate *e = exposable->private; const struct fcft_font *font = exposable->particle->font; + if (e->cache_idx >= 0) { + struct private *priv = exposable->particle->private; + priv->cache[e->cache_idx].in_use = false; + } + if (e->num_glyphs == 0) return; @@ -139,48 +129,153 @@ expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int } } +static uint64_t +sdbm_hash(const char *s) +{ + uint64_t hash = 0; + + for (; *s != '\0'; s++) { + int c = *s; + hash = c + (hash << 6) + (hash << 16) - hash; + } + + return hash; +} + static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { - const struct private *p = particle->private; + struct private *p = (struct private *)particle->private; struct eprivate *e = calloc(1, sizeof(*e)); + struct fcft_font *font = particle->font; - e->text = tags_expand_template(p->text, tags); - e->glyphs = NULL; + wchar_t *wtext = NULL; + char *text = tags_expand_template(p->text, tags); + + e->glyphs = e->allocated_glyphs = NULL; e->num_glyphs = 0; + e->kern_x = NULL; + e->cache_idx = -1; - if (p->max_len > 0) { - const size_t len = strlen(e->text); - if (len > p->max_len) { + uint64_t hash = sdbm_hash(text); - size_t end = p->max_len; - if (end >= 3) { - /* "allocate" room for three dots at the end */ - end -= 3; - } + /* First, check if we have this string cached */ + for (size_t i = 0; i < p->cache_size; i++) { + if (p->cache[i].hash == hash) { + assert(p->cache[i].run != NULL); - /* Mucho importante - don't cut in the middle of a utf8 multibyte */ - while (end > 0 && e->text[end - 1] >> 7) - end--; - - if (p->max_len > 3) { - for (size_t i = 0; i < 3; i++) - e->text[end + i] = '.'; - e->text[end + 3] = '\0'; - } else - e->text[end] = '\0'; + p->cache[i].in_use = true; + e->cache_idx = i; + e->glyphs = p->cache[i].run->glyphs; + e->num_glyphs = p->cache[i].run->count; + e->kern_x = calloc(p->cache[i].run->count, sizeof(e->kern_x[0])); + goto done; } } - char *on_click = tags_expand_template(particle->on_click_template, 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; - struct exposable *exposable = exposable_common_new(particle, on_click); + wtext = malloc((chars + 1) * sizeof(wtext[0])); + mbstowcs(wtext, text, chars + 1); + + /* Truncate, if necessary */ + if (p->max_len > 0) { + const size_t len = wcslen(wtext); + if (len > p->max_len) { + + size_t end = p->max_len; + if (end >= 1) { + /* "allocate" room for three dots at the end */ + end -= 1; + } + + if (p->max_len > 1) { + wtext[end] = L'…'; + wtext[end + 1] = L'\0'; + chars = end + 1; + } else { + wtext[end] = L'\0'; + chars = 0; + } + } + } + + 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( + font, chars, wtext, FCFT_SUBPIXEL_NONE); + + if (run != NULL) { + int w = 0; + for (size_t i = 0; i < run->count; i++) + w += run->glyphs[i]->advance.x; + + ssize_t cache_idx = -1; + for (size_t i = 0; i < p->cache_size; i++) { + if (p->cache[i].run == NULL || !p->cache[i].in_use) { + fcft_text_run_destroy(p->cache[i].run); + cache_idx = i; + break; + } + } + + if (cache_idx < 0) { + size_t new_size = p->cache_size + 1; + struct text_run_cache *new_cache = realloc( + p->cache, new_size * sizeof(new_cache[0])); + + p->cache_size = new_size; + p->cache = new_cache; + cache_idx = new_size - 1; + } + + assert(cache_idx >= 0 && cache_idx < p->cache_size); + p->cache[cache_idx].hash = hash; + p->cache[cache_idx].run = run; + p->cache[cache_idx].width = w; + p->cache[cache_idx].in_use = true; + + e->cache_idx = cache_idx; + e->num_glyphs = run->count; + e->glyphs = run->glyphs; + } + } + + if (e->glyphs == NULL) { + e->allocated_glyphs = malloc(chars * sizeof(e->glyphs[0])); + + /* Convert text to glyph masks/images. */ + for (size_t i = 0; i < chars; i++) { + const struct fcft_glyph *glyph = fcft_glyph_rasterize( + font, wtext[i], FCFT_SUBPIXEL_NONE); + + if (glyph == NULL) + continue; + + e->allocated_glyphs[e->num_glyphs++] = glyph; + + if (i == 0) + continue; + + fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL); + } + + e->glyphs = e->allocated_glyphs; + } + +done: + free(wtext); + free(text); + + struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; - - free(on_click); return exposable; } @@ -188,6 +283,9 @@ static void particle_destroy(struct particle *particle) { struct private *p = particle->private; + for (size_t i = 0; i < p->cache_size; i++) + fcft_text_run_destroy(p->cache[i].run); + free(p->cache); free(p->text); free(p); particle_default_destroy(particle); @@ -199,6 +297,8 @@ string_new(struct particle *common, const char *text, size_t max_len) struct private *p = calloc(1, sizeof(*p)); p->text = strdup(text); p->max_len = max_len; + p->cache_size = 0; + p->cache = NULL; common->private = p; common->destroy = &particle_destroy; diff --git a/subprojects/fcft.wrap b/subprojects/fcft.wrap new file mode 100644 index 0000000..d2709d4 --- /dev/null +++ b/subprojects/fcft.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://codeberg.org/dnkl/fcft.git +revision = master diff --git a/subprojects/tllist.wrap b/subprojects/tllist.wrap new file mode 100644 index 0000000..75f395a --- /dev/null +++ b/subprojects/tllist.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://codeberg.org/dnkl/tllist.git +revision = master diff --git a/tag.c b/tag.c index 02e0794..8ca7e52 100644 --- a/tag.c +++ b/tag.c @@ -446,8 +446,9 @@ tags_expand_template(const char *template, const struct tag_set *tags) } /* Lookup tag */ - const struct tag *tag = tag_for_name(tags, tag_name); - if (tag == NULL) { + const struct tag *tag = NULL; + + if (tag_name == NULL || (tag = tag_for_name(tags, tag_name)) == NULL) { /* No such tag, copy as-is instead */ sbuf_append_at_most(&formatted, template, begin - template + 1); template = begin + 1; @@ -532,3 +533,11 @@ tags_expand_template(const char *template, const struct tag_set *tags) return formatted.s; } + +void +tags_expand_templates(char *expanded[], const char *template[], size_t nmemb, + const struct tag_set *tags) +{ + for (size_t i = 0; i < nmemb; i++) + expanded[i] = tags_expand_template(template[i], tags); +} diff --git a/tag.h b/tag.h index 2c629c5..d6bfe6a 100644 --- a/tag.h +++ b/tag.h @@ -50,3 +50,6 @@ void tag_set_destroy(struct tag_set *set); /* Utility functions */ char *tags_expand_template(const char *template, const struct tag_set *tags); +void tags_expand_templates( + char *expanded[], const char *template[], size_t nmemb, + const struct tag_set *tags);