Merge branch 'master' into releases/1.6

This commit is contained in:
Daniel Eklöf 2021-07-24 13:37:01 +02:00
commit 213974c796
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
69 changed files with 2205 additions and 1070 deletions

View file

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

3
.gitignore vendored
View file

@ -1,4 +1,5 @@
/bld/
/pkg/
/src/
/subprojects/
/subprojects/*
!/subprojects/*.wrap

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ depends=(
'libudev.so'
'json-c'
'libmpdclient'
'fcft>=2.0.0')
'fcft>=2.4.0')
source=()
pkgver() {

View file

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

View file

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

View file

@ -2,12 +2,14 @@
#include "private.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <threads.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/eventfd.h>
@ -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:<unknown>", 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;

View file

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

View file

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

View file

@ -6,9 +6,11 @@
#include <assert.h>
#include <unistd.h>
#include <poll.h>
#include <pthread.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#include <linux/input-event-codes.h>
#include <pixman.h>
#include <wayland-client.h>
@ -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;
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, seat->pointer.x, seat->pointer.y);
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;

View file

@ -5,6 +5,7 @@
#include <unistd.h>
#include <poll.h>
#include <pthread.h>
#include <pixman.h>
#include <xcb/xcb.h>
@ -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;
}

View file

@ -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: <command> */
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},
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

104
doc/yambar-modules-i3.5.scd Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 tags value.
Example:
```
var1|string|hello
var2|int|13
<empty>
var1|string|world
var2|int|37
<empty>
```
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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

145
examples/scripts/dwl-tags.sh Executable file
View file

@ -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<<tag))
tag_name="tag"
declare "${tag_name}_${tag}"
name[tag]="${name[tag]:-${tags[tag]}}"
printf -- '%s\n' "${tag_name}_${tag}|string|${name[tag]}"
if (( "${selectedtags}" & mask )) 2>/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

80
examples/scripts/pacman.sh Executable file
View file

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

View file

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="3">
<interface name="zwlr_layer_shell_v1" version="4">
<description summary="create surfaces that are layers of the desktop">
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 @@
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="3">
<interface name="zwlr_layer_surface_v1" version="4">
<description summary="layer metadata interface">
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.
</description>
<request name="set_size">
@ -189,21 +203,85 @@
<arg name="left" type="int"/>
</request>
<enum name="keyboard_interactivity">
<description summary="types of keyboard interaction possible for a layer shell surface">
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.
</description>
<entry name="none" value="0">
<description summary="no keyboard focus is possible">
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.
</description>
</entry>
<entry name="exclusive" value="1">
<description summary="request exclusive keyboard focus">
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.
</description>
</entry>
<entry name="on_demand" value="2" since="4">
<description summary="request regular keyboard focus semantics">
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.
</description>
</entry>
</enum>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
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.
</description>
<arg name="keyboard_interactivity" type="uint"/>
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
</request>
<request name="get_popup">
@ -288,6 +366,7 @@
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
</enum>
<enum name="anchor" bitfield="true">

@ -1 +1 @@
Subproject commit 16a28885bc92869d8e589e725e7bf018432c47e4
Subproject commit d1598e82240d6e8ca57729495a94d4e11d222033

9
main.c
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,8 +445,7 @@ run(struct module *mod)
continue;
}
if (!update_status(mod))
break;
if (update_status(mod))
bar->refresh(bar);
}
@ -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;
}

View file

@ -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 <pthread.h>
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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@
#include <tllist.h>
#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;
}
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
@ -310,8 +317,6 @@ 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) {
@ -325,10 +330,12 @@ focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
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;
}

View file

@ -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, <)) {
/* Weve reached the timeout, its 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,
};

View file

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

View file

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

View file

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

View file

@ -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,6 +277,7 @@ 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++)
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,9 @@
#include <stdio.h>
#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;
}

View file

@ -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;
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;
uint64_t hash = sdbm_hash(text);
/* 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);
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;
}
}
/* Not in cache - we need to rasterize it. First, convert to wchar */
size_t chars = mbstowcs(NULL, text, 0);
if (chars == (size_t)-1)
goto done;
wtext = malloc((chars + 1) * sizeof(wtext[0]));
mbstowcs(wtext, text, chars + 1);
/* Truncate, if necessary */
if (p->max_len > 0) {
const size_t len = strlen(e->text);
const size_t len = wcslen(wtext);
if (len > p->max_len) {
size_t end = p->max_len;
if (end >= 3) {
if (end >= 1) {
/* "allocate" room for three dots at the end */
end -= 3;
end -= 1;
}
/* 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';
if (p->max_len > 1) {
wtext[end] = L'';
wtext[end + 1] = L'\0';
chars = end + 1;
} else {
wtext[end] = L'\0';
chars = 0;
}
}
}
char *on_click = tags_expand_template(particle->on_click_template, tags);
e->kern_x = calloc(chars, sizeof(e->kern_x[0]));
struct exposable *exposable = exposable_common_new(particle, on_click);
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;

3
subprojects/fcft.wrap Normal file
View file

@ -0,0 +1,3 @@
[wrap-git]
url = https://codeberg.org/dnkl/fcft.git
revision = master

3
subprojects/tllist.wrap Normal file
View file

@ -0,0 +1,3 @@
[wrap-git]
url = https://codeberg.org/dnkl/tllist.git
revision = master

13
tag.c
View file

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

3
tag.h
View file

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