Merge branch 'custom-script'

Closes #11
This commit is contained in:
Daniel Eklöf 2020-11-25 20:42:09 +01:00
commit 4dba602bfd
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
21 changed files with 1002 additions and 77 deletions

View file

@ -12,6 +12,8 @@
(https://codeberg.org/dnkl/yambar/issues/10).
* river: added documentation
(https://codeberg.org/dnkl/yambar/issues/9).
* script: new module, adds support for custom user scripts
(https://codeberg.org/dnkl/yambar/issues/11).
### Deprecated
@ -25,11 +27,14 @@
(https://codeberg.org/dnkl/yambar/issues/12).
* mpd: fix compilation with clang
(https://codeberg.org/dnkl/yambar/issues/16).
* Crash when the alpha component in a color value was 0.
### Security
### Contributors
* [JorwLNKwpH](https://codeberg.org/JorwLNKwpH)
## 1.5.0

View file

@ -62,6 +62,8 @@ bar:
For details, see the man pages (**yambar**(5) is a good start).
Example configurations can be found in [examples](examples/configurations).
## Modules
@ -76,6 +78,9 @@ Available modules:
* mpd
* network
* removables
* river
* script (see script [examples](examples/scripts))
* sway-xkb
* xkb (_XCB backend only_)
* xwindow (_XCB backend only_)

View file

@ -36,19 +36,16 @@ calculate_widths(const struct private *b, int *left, int *center, int *right)
for (size_t i = 0; i < b->left.count; i++) {
struct exposable *e = b->left.exps[i];
assert(e != NULL);
*left += b->left_spacing + e->width + b->right_spacing;
}
for (size_t i = 0; i < b->center.count; i++) {
struct exposable *e = b->center.exps[i];
assert(e != NULL);
*center += b->left_spacing + e->width + b->right_spacing;
}
for (size_t i = 0; i < b->right.count; i++) {
struct exposable *e = b->right.exps[i];
assert(e != NULL);
*right += b->left_spacing + e->width + b->right_spacing;
}
@ -82,30 +79,24 @@ expose(const struct bar *_bar)
for (size_t i = 0; i < bar->left.count; i++) {
struct module *m = bar->left.mods[i];
struct exposable *e = bar->left.exps[i];
if (e != NULL)
e->destroy(e);
bar->left.exps[i] = module_begin_expose(m);
}
for (size_t i = 0; i < bar->center.count; i++) {
struct module *m = bar->center.mods[i];
struct exposable *e = bar->center.exps[i];
if (e != NULL)
e->destroy(e);
bar->center.exps[i] = module_begin_expose(m);
}
for (size_t i = 0; i < bar->right.count; i++) {
struct module *m = bar->right.mods[i];
struct exposable *e = bar->right.exps[i];
if (e != NULL)
e->destroy(e);
bar->right.exps[i] = module_begin_expose(m);
}
@ -307,7 +298,6 @@ destroy(struct bar *bar)
for (size_t i = 0; i < b->left.count; i++) {
struct module *m = b->left.mods[i];
struct exposable *e = b->left.exps[i];
if (e != NULL)
e->destroy(e);
m->destroy(m);
@ -315,7 +305,6 @@ destroy(struct bar *bar)
for (size_t i = 0; i < b->center.count; i++) {
struct module *m = b->center.mods[i];
struct exposable *e = b->center.exps[i];
if (e != NULL)
e->destroy(e);
m->destroy(m);
@ -323,7 +312,6 @@ destroy(struct bar *bar)
for (size_t i = 0; i < b->right.count; i++) {
struct module *m = b->right.mods[i];
struct exposable *e = b->right.exps[i];
if (e != NULL)
e->destroy(e);
m->destroy(m);

View file

@ -121,6 +121,8 @@ seat_destroy(struct seat *seat)
if (seat == NULL)
return;
free(seat->name);
if (seat->pointer.theme != NULL)
wl_cursor_theme_destroy(seat->pointer.theme);
if (seat->wl_pointer != NULL)
@ -134,7 +136,9 @@ seat_destroy(struct seat *seat)
void *
bar_backend_wayland_new(void)
{
return calloc(1, sizeof(struct wayland_backend));
struct wayland_backend *backend = calloc(1, sizeof(struct wayland_backend));
backend->pipe_fds[0] = backend->pipe_fds[1] = -1;
return backend;
}
static void
@ -947,6 +951,11 @@ cleanup(struct bar *_bar)
struct private *bar = _bar->private;
struct wayland_backend *backend = bar->backend.data;
if (backend->pipe_fds[0] >= 0)
close(backend->pipe_fds[0]);
if (backend->pipe_fds[1] >= 0)
close(backend->pipe_fds[1]);
tll_foreach(backend->buffers, it) {
if (it->item.wl_buf != NULL)
wl_buffer_destroy(it->item.wl_buf);

View file

@ -53,6 +53,9 @@ conf_to_color(const struct yml_node *node)
uint16_t blue = hex_byte(&hex[4]);
uint16_t alpha = hex_byte(&hex[6]);
if (alpha == 0)
return (pixman_color_t){0, 0, 0, 0};
alpha |= alpha << 8;
int alpha_div = 0xffff / alpha;

View file

@ -714,11 +714,107 @@ bar:
true:
string:
margin: 5
text: "{id}: {state}"```
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* extenions to the I3 IPC API to monitor input
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*;

124
examples/scripts/cpu.sh Executable file
View file

@ -0,0 +1,124 @@
#!/bin/bash
# cpu.sh - measures CPU usage at a configurable sample interval
#
# Usage: cpu.sh INTERVAL_IN_SECONDS
#
# This script will emit the following tags on stdout (N is the number
# of logical CPUs):
#
# Name Type
# --------------------
# cpu range 0-100
# cpu0 range 0-100
# cpu1 range 0-100
# ...
# cpuN-1 range 0-100
#
# I.e. cpu is the average (or aggregated) CPU usage, while cpuX is a
# specific CPUs usage.
#
# Example configuration (update every second):
#
# - script:
# path: /path/to/cpu.sh
# args: [1]
# content: {string: {text: "{cpu}%"}}
#
interval=${1}
case ${interval} in
''|*[!0-9]*)
echo "interval must be an integer"
exit 1
;;
*)
;;
esac
# Get number of CPUs, by reading /proc/stat
# The output looks like:
#
# cpu A B C D ...
# cpu0 A B C D ...
# cpu1 A B C D ...
# cpuN A B C D ...
#
# The first line is a summary line, accounting *all* CPUs
IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat)
cpu_count=$((${#all_cpu_stats[@]} - 1))
# Arrays of previous idle and total stats, needed to calculate the
# difference between each sample.
prev_idle=()
prev_total=()
for i in $(seq ${cpu_count}); do
prev_idle+=(0)
prev_total+=(0)
done
prev_average_idle=0
prev_average_total=0
while true; do
IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat)
usage=() # CPU usage in percent, 0 <= x <= 100
average_idle=0 # All CPUs idle time since boot
average_total=0 # All CPUs total time since boot
for i in $(seq 0 $((cpu_count - 1))); do
# Split this CPUs stats into an array
stats=($(echo "${all_cpu_stats[$((i + 1))]}"))
# man procfs(5)
user=${stats[1]}
nice=${stats[2]}
system=${stats[3]}
idle=${stats[4]}
iowait=${stats[5]}
irq=${stats[6]}
softirq=${stats[7]}
steal=${stats[8]}
guest=${stats[9]}
guestnice=${stats[10]}
# Guest time already accounted for in user
user=$((user - guest))
nice=$((nice - guestnice))
idle=$((idle + iowait))
total=$((user + nice + system + irq + softirq + idle + steal + guest + guestnice))
average_idle=$((average_idle + idle))
average_total=$((average_total + total))
# Diff since last sample
diff_idle=$((idle - prev_idle[i]))
diff_total=$((total - prev_total[i]))
usage[i]=$((100 * (diff_total - diff_idle) / diff_total))
prev_idle[i]=${idle}
prev_total[i]=${total}
done
diff_average_idle=$((average_idle - prev_average_idle))
diff_average_total=$((average_total - prev_average_total))
average_usage=$((100 * (diff_average_total - diff_average_idle) / diff_average_total))
prev_average_idle=${average_idle}
prev_average_total=${average_total}
echo "cpu|range:0-100|${average_usage}"
for i in $(seq 0 $((cpu_count - 1))); do
echo "cpu${i}|range:0-100|${usage[i]}"
done
echo ""
sleep "${interval}"
done

20
main.c
View file

@ -326,7 +326,7 @@ main(int argc, char *const *argv)
thrd_t bar_thread;
thrd_create(&bar_thread, (int (*)(void *))bar->run, bar);
/* Now unblock. We should be only thread receiving SIGINT */
/* Now unblock. We should be only thread receiving SIGINT/SIGTERM */
pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
if (pid_file != NULL) {
@ -336,15 +336,17 @@ main(int argc, char *const *argv)
while (!aborted) {
struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}};
int r __attribute__((unused)) = poll(fds, 1, -1);
int r __attribute__((unused)) = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
/*
* Either the bar aborted (triggering the abort_fd), or user
* killed us (triggering the signal handler which sets
* 'aborted')
*/
assert(aborted || r == 1);
break;
if (fds[0].revents & (POLLIN | POLLHUP)) {
/*
* Either the bar aborted (triggering the abort_fd), or user
* killed us (triggering the signal handler which sets
* 'aborted')
*/
assert(aborted || r == 1);
break;
}
}
if (aborted)

View file

@ -291,7 +291,10 @@ update_status(struct module *mod, int capacity_fd, int energy_fd, int power_fd,
const char *status = readline_from_fd(status_fd);
enum state state;
if (strcmp(status, "Full") == 0)
if (status == NULL) {
LOG_WARN("failed to read battery state");
state = STATE_DISCHARGING;
} else if (strcmp(status, "Full") == 0)
state = STATE_FULL;
else if (strcmp(status, "Charging") == 0)
state = STATE_CHARGING;

View file

@ -9,7 +9,7 @@ mpd = dependency('libmpdclient')
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
# Module name -> (source-list, dep-list)
deps = {
mod_data = {
'alsa': [[], [m, alsa]],
'backlight': [[], [m, udev]],
'battery': [[], [udev]],
@ -19,11 +19,12 @@ deps = {
'mpd': [[], [mpd]],
'network': [[], []],
'removables': [[], [dynlist, udev]],
'sway_xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
'script': [[], []],
'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
}
if backend_x11
deps += {
mod_data += {
'xkb': [[], [xcb_stuff, xcb_xkb]],
'xwindow': [[], [xcb_stuff]],
}
@ -48,25 +49,25 @@ if backend_wayland
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
endforeach
deps += {
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], []],
mod_data += {
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]],
}
endif
foreach mod, data : deps
foreach mod, data : mod_data
sources = data[0]
dep = data[1]
deps = data[1]
if plugs_as_libs
shared_module(mod, '@0@.c'.format(mod), sources,
dependencies: [module_sdk] + dep,
dependencies: [module_sdk] + deps,
name_prefix: 'module_',
install: true,
install_dir: join_paths(get_option('libdir'), 'yambar'))
else
modules += [declare_dependency(
sources: ['@0@.c'.format(mod)] + sources,
dependencies: [module_sdk] + dep,
compile_args: '-DHAVE_PLUGIN_@0@'.format(mod))]
dependencies: [module_sdk] + deps,
compile_args: '-DHAVE_PLUGIN_@0@'.format(mod.underscorify()))]
endif
endforeach

View file

@ -90,7 +90,8 @@ content(struct module *mod)
}
}
const size_t seat_count = m->title != NULL ? tll_length(m->seats) : 0;
const size_t seat_count = m->title != NULL && !m->is_starting_up
? tll_length(m->seats) : 0;
struct exposable *tag_parts[32 + seat_count];
for (unsigned i = 0; i < 32; i++) {
@ -122,7 +123,7 @@ content(struct module *mod)
tag_set_destroy(&tags);
}
if (m->title != NULL) {
if (m->title != NULL && !m->is_starting_up) {
size_t i = 32;
tll_foreach(m->seats, it) {
const struct seat *seat = &it->item;
@ -565,6 +566,10 @@ run(struct module *mod)
}
wl_display_roundtrip(display);
bool unlock_at_exit = true;
mtx_lock(&mod->lock);
m->is_starting_up = false;
tll_foreach(m->outputs, it)
@ -572,6 +577,9 @@ run(struct module *mod)
tll_foreach(m->seats, it)
instantiate_seat(&it->item);
unlock_at_exit = false;
mtx_unlock(&mod->lock);
while (true) {
wl_display_flush(display);
@ -618,6 +626,9 @@ out:
wl_registry_destroy(registry);
if (display != NULL)
wl_display_disconnect(display);
if (unlock_at_exit)
mtx_unlock(&mod->lock);
return ret;
}

639
modules/script.c Normal file
View file

@ -0,0 +1,639 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/timerfd.h>
#define LOG_MODULE "script"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#include "../config.h"
#include "../config-verify.h"
#include "../module.h"
#include "../plugin.h"
struct private {
char *path;
size_t argc;
char **argv;
struct particle *content;
struct tag_set tags;
struct {
char *data;
size_t sz;
size_t idx;
} recv_buf;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->content->destroy(m->content);
struct tag **tag_array = m->tags.tags;
tag_set_destroy(&m->tags);
free(tag_array);
for (size_t i = 0; i < m->argc; i++)
free(m->argv[i]);
free(m->argv);
free(m->recv_buf.data);
free(m->path);
free(m);
module_default_destroy(mod);
}
static struct exposable *
content(struct module *mod)
{
const struct private *m = mod->private;
mtx_lock(&mod->lock);
struct exposable *e = m->content->instantiate(m->content, &m->tags);
mtx_unlock(&mod->lock);
return e;
}
static struct tag *
process_line(struct module *mod, const char *line, size_t len)
{
char *name = NULL;
char *value = NULL;
const char *_name = line;
const char *type = memchr(line, '|', len);
if (type == NULL)
goto bad_tag;
size_t name_len = type - _name;
type++;
const char *_value = memchr(type, '|', len - name_len - 1);
if (_value == NULL)
goto bad_tag;
size_t type_len = _value - type;
_value++;
size_t value_len = line + len - _value;
LOG_DBG("%.*s: name=\"%.*s\", type=\"%.*s\", value=\"%.*s\"",
(int)len, line,
(int)name_len, _name, (int)type_len, type, (int)value_len, _value);
name = malloc(name_len + 1);
memcpy(name, _name, name_len);
name[name_len] = '\0';
value = malloc(value_len + 1);
memcpy(value, _value, value_len);
value[value_len] = '\0';
struct tag *tag = NULL;
if (type_len == 6 && memcmp(type, "string", 6) == 0)
tag = tag_new_string(mod, name, value);
else if (type_len == 3 && memcmp(type, "int", 3) == 0) {
errno = 0;
char *end;
long v = strtol(value, &end, 0);
if (errno != 0 || *end != '\0') {
LOG_ERR("tag value is not an integer: %s", value);
goto bad_tag;
}
tag = tag_new_int(mod, name, v);
}
else if (type_len == 4 && memcmp(type, "bool", 4) == 0) {
bool v;
if (strcmp(value, "true") == 0)
v = true;
else if (strcmp(value, "false") == 0)
v = false;
else {
LOG_ERR("tag value is not a boolean: %s", value);
goto bad_tag;
}
tag = tag_new_bool(mod, name, v);
}
else if (type_len == 5 && memcmp(type, "float", 5) == 0) {
errno = 0;
char *end;
double v = strtod(value, &end);
if (errno != 0 || *end != '\0') {
LOG_ERR("tag value is not a float: %s", value);
goto bad_tag;
}
tag = tag_new_float(mod, name, v);
}
else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) ||
(type_len > 9 && memcmp(type, "realtime:", 9 == 0)))
{
const char *_start = type + 6;
const char *split = memchr(_start, '-', type_len - 6);
if (split == NULL || split == _start || (split + 1) - type >= type_len) {
LOG_ERR(
"tag range delimiter ('-') not found in type: %.*s",
(int)type_len, type);
goto bad_tag;
}
const char *_end = split + 1;
size_t start_len = split - _start;
size_t end_len = type + type_len - _end;
long start = 0;
for (size_t i = 0; i < start_len; i++) {
if (!(_start[i] >= '0' && _start[i] <= '9')) {
LOG_ERR(
"tag range start is not an integer: %.*s",
(int)start_len, _start);
goto bad_tag;
}
start *= 10;
start += _start[i] - '0';
}
long end = 0;
for (size_t i = 0; i < end_len; i++) {
if (!(_end[i] >= '0' && _end[i] < '9')) {
LOG_ERR(
"tag range end is not an integer: %.*s",
(int)end_len, _end);
goto bad_tag;
}
end *= 10;
end += _end[i] - '0';
}
if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) {
LOG_ERR("unimplemented: realtime tag");
goto bad_tag;
}
errno = 0;
char *vend;
long v = strtol(value, &vend, 0);
if (errno != 0 || *vend != '\0') {
LOG_ERR("tag value is not an integer: %s", value);
goto bad_tag;
}
if (v < start || v > end) {
LOG_ERR("tag value is outside range: %ld <= %ld <= %ld",
start, v, end);
goto bad_tag;
}
tag = tag_new_int_range(mod, name, v, start, end);
}
else {
goto bad_tag;
}
free(name);
free(value);
return tag;
bad_tag:
LOG_ERR("invalid tag: %.*s", (int)len, line);
free(name);
free(value);
return NULL;
}
static void
process_transaction(struct module *mod, size_t size)
{
struct private *m = mod->private;
mtx_lock(&mod->lock);
size_t left = size;
const char *line = m->recv_buf.data;
size_t line_count = 0;
{
const char *p = line;
while ((p = memchr(p, '\n', size - (p - line))) != NULL) {
p++;
line_count++;
}
}
struct tag **old_tag_array = m->tags.tags;
tag_set_destroy(&m->tags);
free(old_tag_array);
m->tags.tags = calloc(line_count, sizeof(m->tags.tags[0]));
m->tags.count = line_count;
size_t idx = 0;
while (left > 0) {
char *line_end = memchr(line, '\n', left);
assert(line_end != NULL);
size_t line_len = line_end - line;
struct tag *tag = process_line(mod, line, line_len);
if (tag != NULL)
m->tags.tags[idx++] = tag;
left -= line_len + 1;
line += line_len + 1;
}
m->tags.count = idx;
mtx_unlock(&mod->lock);
mod->bar->refresh(mod->bar);
}
static bool
data_received(struct module *mod, const char *data, size_t len)
{
struct private *m = mod->private;
if (len > m->recv_buf.sz - m->recv_buf.idx) {
size_t new_sz = m->recv_buf.sz == 0 ? 1024 : m->recv_buf.sz * 2;
char *new_buf = realloc(m->recv_buf.data, new_sz);
if (new_buf == NULL)
return false;
m->recv_buf.data = new_buf;
m->recv_buf.sz = new_sz;
}
assert(m->recv_buf.sz >= m->recv_buf.idx);
assert(m->recv_buf.sz - m->recv_buf.idx >= len);
memcpy(&m->recv_buf.data[m->recv_buf.idx], data, len);
m->recv_buf.idx += len;
const char *eot = memmem(m->recv_buf.data, m->recv_buf.idx, "\n\n", 2);
if (eot == NULL) {
/* End of transaction not yet available */
return true;
}
const size_t transaction_size = eot - m->recv_buf.data + 1;
process_transaction(mod, transaction_size);
assert(m->recv_buf.idx >= transaction_size + 1);
memmove(m->recv_buf.data,
&m->recv_buf.data[transaction_size + 1],
m->recv_buf.idx - (transaction_size + 1));
m->recv_buf.idx -= transaction_size + 1;
return true;
}
static int
run_loop(struct module *mod, pid_t pid, int comm_fd)
{
int ret = 0;
while (true) {
struct pollfd fds[] = {
{.fd = mod->abort_fd, .events = POLLIN},
{.fd = comm_fd, .events = POLLIN},
};
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
if (r < 0) {
if (errno == EINTR)
continue;
LOG_ERRNO("failed to poll");
break;
}
if (fds[1].revents & POLLIN) {
char data[4096];
ssize_t amount = read(comm_fd, data, sizeof(data));
if (amount < 0) {
LOG_ERRNO("failed to read from script");
break;
}
LOG_DBG("recv: \"%.*s\"", (int)amount, data);
data_received(mod, data, amount);
}
if (fds[0].revents & POLLHUP) {
/* Aborted */
break;
}
if (fds[1].revents & POLLHUP) {
/* Child's stdout closed */
LOG_DBG("script pipe closed (script terminated?)");
break;
}
if (fds[0].revents & POLLIN) {
/* Aborted */
break;
}
}
return ret;
}
static int
run(struct module *mod)
{
struct private *m = mod->private;
/* Pipe to detect exec() failures */
int exec_pipe[2];
if (pipe2(exec_pipe, O_CLOEXEC) < 0) {
LOG_ERRNO("failed to create pipe");
return -1;
}
/* Stdout redirection pipe */
int comm_pipe[2];
if (pipe(comm_pipe) < 0) {
LOG_ERRNO("failed to create stdin/stdout redirection pipe");
close(exec_pipe[0]);
close(exec_pipe[1]);
return -1;
}
int pid = fork();
if (pid < 0) {
LOG_ERRNO("failed to fork");
close(comm_pipe[0]);
close(comm_pipe[1]);
close(exec_pipe[0]);
close(exec_pipe[1]);
return -1;
}
if (pid == 0) {
/* Child */
/* Construct argv for execvp() */
char *argv[1 + m->argc + 1];
argv[0] = m->path;
for (size_t i = 0; i < m->argc; i++)
argv[i + 1] = m->argv[i];
argv[1 + m->argc] = NULL;
/* Restore signal handlers and signal mask */
sigset_t mask;
sigemptyset(&mask);
const struct sigaction sa = {.sa_handler = SIG_DFL};
if (sigaction(SIGINT, &sa, NULL) < 0 ||
sigaction(SIGTERM, &sa, NULL) < 0 ||
sigaction(SIGCHLD, &sa, NULL) < 0 ||
sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
{
goto fail;
}
/* New process group, so that we can use killpg() */
setpgid(0, 0);
/* Close pipe read ends */
close(exec_pipe[0]);
close(comm_pipe[0]);
/* Re-direct stdin/stdout */
int dev_null = open("/dev/null", O_RDONLY);
if (dev_null < 0)
goto fail;
if (dup2(dev_null, STDIN_FILENO) < 0 ||
dup2(comm_pipe[1], STDOUT_FILENO) < 0)
{
goto fail;
}
/* We're done with the redirection pipe */
close(comm_pipe[1]);
comm_pipe[1] = -1;
/* Close *all* other FDs */
for (int i = STDERR_FILENO + 1; i < 65536; i++) {
if (i == exec_pipe[1]) {
/* Needed for error reporting. Automatically closed
* when execvp() succeeds */
continue;
}
close(i);
}
execvp(m->path, argv);
fail:
(void)!write(exec_pipe[1], &errno, sizeof(errno));
close(exec_pipe[1]);
if (comm_pipe[1] >= 0)
close(comm_pipe[1]);
_exit(errno);
}
/* Close pipe write ends */
close(exec_pipe[1]);
close(comm_pipe[1]);
int _errno;
static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch");
/* Wait for errno from child, or FD being closed in execvp() */
int r = read(exec_pipe[0], &_errno, sizeof(_errno));
close(exec_pipe[0]);
if (r < 0) {
LOG_ERRNO("failed to read from pipe");
close(comm_pipe[0]);
return -1;
}
if (r > 0) {
LOG_ERRNO_P("%s: failed to start", _errno, m->path);
close(comm_pipe[0]);
waitpid(pid, NULL, 0);
return -1;
}
/* Pipe was closed. I.e. execvp() succeeded */
assert(r == 0);
LOG_DBG("script running under PID=%u", pid);
int ret = run_loop(mod, pid, comm_pipe[0]);
close(comm_pipe[0]);
if (waitpid(pid, NULL, WNOHANG) == 0) {
static const struct {
int signo;
int timeout;
const char *name;
} sig_info[] = {
{SIGINT, 2, "SIGINT"},
{SIGTERM, 5, "SIGTERM"},
{SIGKILL, 0, "SIGKILL"},
};
for (size_t i = 0; i < sizeof(sig_info) / sizeof(sig_info[0]); i++) {
struct timeval start;
gettimeofday(&start, NULL);
const int signo = sig_info[i].signo;
const int timeout = sig_info[i].timeout;
const char *const name __attribute__((unused)) = sig_info[i].name;
LOG_DBG("sending %s to PID=%u (timeout=%ds)", name, pid, timeout);
killpg(pid, signo);
/*
* Child is unlikely to terminate *immediately*. Wait a
* *short* period of time before checking waitpid() the
* first time
*/
usleep(10000);
pid_t waited_pid;
while ((waited_pid = waitpid(
pid, NULL, timeout > 0 ? WNOHANG : 0)) == 0)
{
struct timeval now;
gettimeofday(&now, NULL);
struct timeval elapsed;
timersub(&now, &start, &elapsed);
if (elapsed.tv_sec >= timeout)
break;
/* Don't spinning */
thrd_yield();
usleep(100000); /* 100ms */
}
if (waited_pid == pid) {
/* Child finally dead */
break;
}
}
} else
LOG_DBG("PID=%u already terminated", pid);
return ret;
}
static struct module *
script_new(const char *path, size_t argc, const char *const argv[static argc],
struct particle *_content)
{
struct private *m = calloc(1, sizeof(*m));
m->path = strdup(path);
m->content = _content;
m->argc = argc;
m->argv = malloc(argc * sizeof(m->argv[0]));
for (size_t i = 0; i < argc; i++)
m->argv[i] = strdup(argv[i]);
struct module *mod = module_common_new();
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
return mod;
}
static struct module *
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");
size_t argc = args != NULL ? yml_list_length(args) : 0;
const char *argv[argc];
if (args != NULL) {
size_t i = 0;
for (struct yml_list_iter iter = yml_list_iter(args);
iter.node != NULL;
yml_list_next(&iter), i++)
{
argv[i] = yml_value_as_string(iter.node);
}
}
return script_new(
yml_value_as_string(path), argc, argv, conf_to_particle(c, inherited));
}
static bool
conf_verify_path(keychain_t *chain, const struct yml_node *node)
{
if (!conf_verify_string(chain, node))
return false;
const char *path = yml_value_as_string(node);
if (strlen(path) < 1 || path[0] != '/') {
LOG_ERR("%s: path must be absolute", conf_err_prefix(chain, node));
return false;
}
return true;
}
static bool
conf_verify_args(keychain_t *chain, const struct yml_node *node)
{
return conf_verify_list(chain, node, &conf_verify_string);
}
static bool
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},
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_script_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_script_iface")));
#endif

View file

@ -219,14 +219,25 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar,
LOG_DBG("executing on-click handler: %s", cmd);
sigset_t mask;
sigemptyset(&mask);
const struct sigaction sa = {.sa_handler = SIG_DFL};
if (sigaction(SIGINT, &sa, NULL) < 0 ||
sigaction(SIGTERM, &sa, NULL) < 0 ||
sigaction(SIGCHLD, &sa, NULL) < 0 ||
sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
{
goto fail;
}
/* Redirect stdin/stdout/stderr to /dev/null */
int dev_null_r = open("/dev/null", O_RDONLY | O_CLOEXEC);
int dev_null_w = open("/dev/null", O_WRONLY | O_CLOEXEC);
if (dev_null_r == -1 || dev_null_w == -1) {
LOG_ERRNO("/dev/null: failed to open");
(void)!write(pipe_fds[1], &errno, sizeof(errno));
_exit(1);
goto fail;
}
if (dup2(dev_null_r, STDIN_FILENO) == -1 ||
@ -234,15 +245,20 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar,
dup2(dev_null_w, STDERR_FILENO) == -1)
{
LOG_ERRNO("failed to redirect stdin/stdout/stderr");
(void)!write(pipe_fds[1], &errno, sizeof(errno));
_exit(1);
goto fail;
}
/* Close *all* other FDs (e.g. script modules' FDs) */
for (int i = STDERR_FILENO + 1; i < 65536; i++)
close(i);
execvp(argv[0], argv);
fail:
/* Signal failure to parent process */
(void)!write(pipe_fds[1], &errno, sizeof(errno));
_exit(1);
close(pipe_fds[1]);
_exit(errno);
break;
default:

View file

@ -118,6 +118,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
for (size_t i = 0; i < p->count; i++) {
const struct particle *pp = p->particles[i];
e->exposables[i] = pp->instantiate(pp, tags);
assert(e->exposables[i] != NULL);
}
char *on_click = tags_expand_template(particle->on_click_template, tags);

View file

@ -8,6 +8,7 @@
#include "../config-verify.h"
#include "../particle.h"
#include "../plugin.h"
#include "dynlist.h"
struct particle_map {
const char *tag_value;
@ -87,11 +88,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
{
const struct private *p = particle->private;
const struct tag *tag = tag_for_name(tags, p->tag);
assert(tag != NULL || p->default_particle != NULL);
if (tag == NULL)
return p->default_particle->instantiate(p->default_particle, tags);
if (tag == NULL) {
return p->default_particle != NULL
? p->default_particle->instantiate(p->default_particle, tags)
: dynlist_exposable_new(NULL, 0, 0, 0);
}
const char *tag_value = tag->as_string(tag);
struct particle *pp = NULL;
@ -106,13 +108,16 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
break;
}
if (pp == NULL) {
assert(p->default_particle != NULL);
pp = p->default_particle;
}
struct eprivate *e = calloc(1, sizeof(*e));
e->exposable = pp->instantiate(pp, tags);
if (pp != NULL)
e->exposable = pp->instantiate(pp, tags);
else if (p->default_particle != NULL)
e->exposable = p->default_particle->instantiate(p->default_particle, tags);
else
e->exposable = dynlist_exposable_new(NULL, 0, 0, 0);
assert(e->exposable != NULL);
char *on_click = tags_expand_template(particle->on_click_template, tags);
struct exposable *exposable = exposable_common_new(particle, on_click);

View file

@ -1,21 +1,5 @@
particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
particles = []
foreach particle : ['empty', 'list', 'map', 'progress-bar', 'ramp', 'string']
if plugs_as_libs
shared_module('@0@'.format(particle), '@0@.c'.format(particle),
dependencies: particle_sdk,
name_prefix: 'particle_',
install: true,
install_dir: join_paths(get_option('libdir'), 'yambar'))
else
particles += [declare_dependency(
sources: '@0@.c'.format(particle),
dependencies: particle_sdk,
compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))]
endif
endforeach
dynlist_lib = build_target(
'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk,
target_type: plugs_as_libs ? 'shared_library' : 'static_library',
@ -25,3 +9,29 @@ dynlist_lib = build_target(
)
dynlist = declare_dependency(link_with: dynlist_lib)
# Particle name -> dep-list
deps = {
'empty': [],
'list': [],
'map': [dynlist],
'progress-bar': [],
'ramp': [],
'string': [],
}
particles = []
foreach particle, particle_deps : deps
if plugs_as_libs
shared_module('@0@'.format(particle), '@0@.c'.format(particle),
dependencies: [particle_sdk] + particle_deps,
name_prefix: 'particle_',
install: true,
install_dir: join_paths(get_option('libdir'), 'yambar'))
else
particles += [declare_dependency(
sources: '@0@.c'.format(particle),
dependencies: [particle_sdk] + particle_deps,
compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))]
endif
endforeach

View file

@ -179,13 +179,13 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
{
const struct private *p = particle->private;
const struct tag *tag = tag_for_name(tags, p->tag);
assert(tag != NULL);
long value = tag->as_int(tag);
long min = tag->min(tag);
long max = tag->max(tag);
long value = tag != NULL ? tag->as_int(tag) : 0;
long min = tag != NULL ? tag->min(tag) : 0;
long max = tag != NULL ? tag->max(tag) : 0;
LOG_DBG("%s: value=%ld, min=%ld, max=%ld", tag->name(tag), value, min, max);
LOG_DBG("%s: value=%ld, min=%ld, max=%ld",
tag != NULL ? tag->name(tag) : "<no tag>", value, min, max);
long fill_count = max == min ? 0 : p->width * value / (max - min);
long empty_count = p->width - fill_count;
@ -210,6 +210,8 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
epriv->exposables[idx++] = p->end_marker->instantiate(p->end_marker, tags);
assert(idx == epriv->count);
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);
@ -222,6 +224,9 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
exposable->expose = &expose;
exposable->on_mouse = &on_mouse;
if (tag == NULL)
return exposable;
enum tag_realtime_unit rt = tag->realtime(tag);
if (rt == TAG_REALTIME_NONE)

View file

@ -95,13 +95,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
{
const struct private *p = particle->private;
const struct tag *tag = tag_for_name(tags, p->tag);
assert(tag != NULL);
assert(p->count > 0);
long value = tag->as_int(tag);
long min = tag->min(tag);
long max = tag->max(tag);
long value = tag != NULL ? tag->as_int(tag) : 0;
long min = tag != NULL ? tag->min(tag) : 0;
long max = tag != NULL ? tag->max(tag) : 0;
assert(value >= min && value <= max);
assert(max >= min);
@ -122,6 +121,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
struct eprivate *e = calloc(1, sizeof(*e));
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);

View file

@ -43,6 +43,7 @@ EXTERN_MODULE(network);
EXTERN_MODULE(removables);
EXTERN_MODULE(river);
EXTERN_MODULE(sway_xkb);
EXTERN_MODULE(script);
EXTERN_MODULE(xkb);
EXTERN_MODULE(xwindow);
@ -119,6 +120,7 @@ init(void)
REGISTER_CORE_MODULE(river, river);
#endif
REGISTER_CORE_MODULE(sway-xkb, sway_xkb);
REGISTER_CORE_MODULE(script, script);
#if defined(HAVE_PLUGIN_xkb)
REGISTER_CORE_MODULE(xkb, xkb);
#endif