yambar/bar/bar.c
Daniel Eklöf 9353aa14fe
bar: set clip region before calling the particles’ expose() methods
This ensures particles that are “too wide” doesn’t try to render
outside the bar, possibly overrunning both margins and borders. Or
worse, crashes yambar.

Closes #198
2022-06-05 00:33:30 +02:00

517 lines
16 KiB
C

#include "bar.h"
#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>
#define LOG_MODULE "bar"
#define LOG_ENABLE_DBG 0
#include "../log.h"
#if defined(ENABLE_X11)
#include "xcb.h"
#endif
#if defined(ENABLE_WAYLAND)
#include "wayland.h"
#endif
#define max(x, y) ((x) > (y) ? (x) : (y))
/*
* Calculate total width of left/center/rigth groups.
* Note: begin_expose() must have been called
*/
static void
calculate_widths(const struct private *b, int *left, int *center, int *right)
{
*left = 0;
*center = 0;
*right = 0;
for (size_t i = 0; i < b->left.count; i++) {
struct exposable *e = b->left.exps[i];
if (e->width > 0)
*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];
if (e->width > 0)
*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];
if (e->width > 0)
*right += b->left_spacing + e->width + b->right_spacing;
}
/* No spacing on the edges (that's what the margins are for) */
if (*left > 0)
*left -= b->left_spacing + b->right_spacing;
if (*center > 0)
*center -= b->left_spacing + b->right_spacing;
if (*right > 0)
*right -= b->left_spacing + b->right_spacing;
assert(*left >= 0);
assert(*center >= 0);
assert(*right >= 0);
}
static void
expose(const struct bar *_bar)
{
const struct private *bar = _bar->private;
pixman_image_t *pix = bar->pix;
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, pix, &bar->background, 1,
&(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border});
pixman_image_fill_rectangles(
PIXMAN_OP_OVER, pix, &bar->border.color, 4,
(pixman_rectangle16_t[]){
/* Left */
{0, 0, bar->border.left_width, bar->height_with_border},
/* Right */
{bar->width - bar->border.right_width,
0, bar->border.right_width, bar->height_with_border},
/* Top */
{bar->border.left_width,
0,
bar->width - bar->border.left_width - bar->border.right_width,
bar->border.top_width},
/* Bottom */
{bar->border.left_width,
bar->height_with_border - bar->border.bottom_width,
bar->width - bar->border.left_width - bar->border.right_width,
bar->border.bottom_width},
});
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);
assert(bar->left.exps[i]->width >= 0);
}
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);
assert(bar->center.exps[i]->width >= 0);
}
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);
assert(bar->right.exps[i]->width >= 0);
}
int left_width, center_width, right_width;
calculate_widths(bar, &left_width, &center_width, &right_width);
int y = bar->border.top_width;
int x = bar->border.left_width + bar->left_margin - bar->left_spacing;
pixman_region32_t clip;
pixman_region32_init_rect(
&clip,
bar->border.left_width + bar->left_margin,
bar->border.top_width,
(bar->width -
bar->left_margin - bar->right_margin -
bar->border.left_width - bar->border.right_width),
bar->height);
pixman_image_set_clip_region32(pix, &clip);
pixman_region32_fini(&clip);
for (size_t i = 0; i < bar->left.count; i++) {
const struct exposable *e = bar->left.exps[i];
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
if (e->width > 0)
x += bar->left_spacing + e->width + bar->right_spacing;
}
x = bar->width / 2 - center_width / 2 - bar->left_spacing;
for (size_t i = 0; i < bar->center.count; i++) {
const struct exposable *e = bar->center.exps[i];
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
if (e->width > 0)
x += bar->left_spacing + e->width + bar->right_spacing;
}
x = bar->width - (
right_width +
bar->left_spacing +
bar->right_margin +
bar->border.right_width);
for (size_t i = 0; i < bar->right.count; i++) {
const struct exposable *e = bar->right.exps[i];
e->expose(e, pix, x + bar->left_spacing, y, bar->height);
if (e->width > 0)
x += bar->left_spacing + e->width + bar->right_spacing;
}
bar->backend.iface->commit(_bar);
}
static void
refresh(const struct bar *bar)
{
const struct private *b = bar->private;
b->backend.iface->refresh(bar);
}
static void
set_cursor(struct bar *bar, const char *cursor)
{
struct private *b = bar->private;
b->backend.iface->set_cursor(bar, cursor);
}
static const char *
output_name(const struct bar *bar)
{
const struct private *b = bar->private;
return b->backend.iface->output_name(bar);
}
static void
on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn,
int x, int y)
{
struct private *bar = _bar->private;
if ((y < bar->border.top_width ||
y >= (bar->height_with_border - bar->border.bottom_width)) ||
(x < bar->border.left_width || x >= (bar->width - bar->border.right_width)))
{
set_cursor(_bar, "left_ptr");
return;
}
int left_width, center_width, right_width;
calculate_widths(bar, &left_width, &center_width, &right_width);
int mx = bar->border.left_width + bar->left_margin - bar->left_spacing;
for (size_t i = 0; i < bar->left.count; i++) {
struct exposable *e = bar->left.exps[i];
if (e->width == 0)
continue;
mx += bar->left_spacing;
if (x >= mx && x < mx + e->width) {
if (e->on_mouse != NULL)
e->on_mouse(e, _bar, event, btn, x - mx, y);
return;
}
mx += e->width + bar->right_spacing;
}
mx = bar->width / 2 - center_width / 2 - bar->left_spacing;
for (size_t i = 0; i < bar->center.count; i++) {
struct exposable *e = bar->center.exps[i];
if (e->width == 0)
continue;
mx += bar->left_spacing;
if (x >= mx && x < mx + e->width) {
if (e->on_mouse != NULL)
e->on_mouse(e, _bar, event, btn, x - mx, y);
return;
}
mx += e->width + bar->right_spacing;
}
mx = bar->width - (right_width +
bar->left_spacing +
bar->right_margin +
bar->border.right_width);
for (size_t i = 0; i < bar->right.count; i++) {
struct exposable *e = bar->right.exps[i];
if (e->width == 0)
continue;
mx += bar->left_spacing;
if (x >= mx && x < mx + e->width) {
if (e->on_mouse != NULL)
e->on_mouse(e, _bar, event, btn, x - mx, y);
return;
}
mx += e->width + bar->right_spacing;
}
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)
{
struct private *bar = _bar->private;
bar->height_with_border =
bar->height + bar->border.top_width + bar->border.bottom_width;
if (!bar->backend.iface->setup(_bar)) {
bar->backend.iface->cleanup(_bar);
if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t))
LOG_ERRNO("failed to signal abort");
return 1;
}
set_cursor(_bar, "left_ptr");
expose(_bar);
/* Start modules */
thrd_t thrd_left[max(bar->left.count, 1)];
thrd_t thrd_center[max(bar->center.count, 1)];
thrd_t thrd_right[max(bar->right.count, 1)];
for (size_t i = 0; i < bar->left.count; i++) {
struct module *mod = bar->left.mods[i];
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");
bar->backend.iface->loop(_bar, &expose, &on_mouse);
LOG_DBG("shutting down");
/* Wait for modules to terminate */
int ret = 0;
int mod_ret;
for (size_t i = 0; i < bar->left.count; i++) {
thrd_join(thrd_left[i], &mod_ret);
if (mod_ret != 0)
LOG_ERR("module: LEFT #%zu: non-zero exit value: %d", i, mod_ret);
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
}
for (size_t i = 0; i < bar->center.count; i++) {
thrd_join(thrd_center[i], &mod_ret);
if (mod_ret != 0)
LOG_ERR("module: CENTER #%zu: non-zero exit value: %d", i, mod_ret);
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
}
for (size_t i = 0; i < bar->right.count; i++) {
thrd_join(thrd_right[i], &mod_ret);
if (mod_ret != 0)
LOG_ERR("module: RIGHT #%zu: non-zero exit value: %d", i, mod_ret);
ret = ret == 0 && mod_ret != 0 ? mod_ret : ret;
}
LOG_DBG("modules joined");
bar->backend.iface->cleanup(_bar);
LOG_DBG("bar exiting");
return ret;
}
static void
destroy(struct bar *bar)
{
struct private *b = bar->private;
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);
}
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);
}
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);
}
free(b->left.mods);
free(b->left.exps);
free(b->center.mods);
free(b->center.exps);
free(b->right.mods);
free(b->right.exps);
free(b->monitor);
free(b->backend.data);
free(bar->private);
free(bar);
}
struct bar *
bar_new(const struct bar_config *config)
{
void *backend_data = NULL;
const struct backend *backend_iface = NULL;
switch (config->backend) {
case BAR_BACKEND_AUTO:
#if defined(ENABLE_X11) && !defined(ENABLE_WAYLAND)
backend_data = bar_backend_xcb_new();
backend_iface = &xcb_backend_iface;
#elif !defined(ENABLE_X11) && defined(ENABLE_WAYLAND)
backend_data = bar_backend_wayland_new();
backend_iface = &wayland_backend_iface;
#else
if (getenv("WAYLAND_DISPLAY") != NULL) {
backend_data = bar_backend_wayland_new();
backend_iface = &wayland_backend_iface;
} else {
backend_data = bar_backend_xcb_new();
backend_iface = &xcb_backend_iface;
}
#endif
break;
case BAR_BACKEND_XCB:
#if defined(ENABLE_X11)
backend_data = bar_backend_xcb_new();
backend_iface = &xcb_backend_iface;
#else
LOG_ERR("yambar was compiled without the XCB backend");
return NULL;
#endif
break;
case BAR_BACKEND_WAYLAND:
#if defined(ENABLE_WAYLAND)
backend_data = bar_backend_wayland_new();
backend_iface = &wayland_backend_iface;
#else
LOG_ERR("yambar was compiled without the Wayland backend");
return NULL;
#endif
break;
}
if (backend_data == NULL)
return NULL;
struct private *priv = calloc(1, sizeof(*priv));
priv->monitor = config->monitor != NULL ? strdup(config->monitor) : NULL;
priv->layer = config->layer;
priv->location = config->location;
priv->height = config->height;
priv->background = config->background;
priv->left_spacing = config->left_spacing;
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.left_width = config->border.left_width;
priv->border.right_width = config->border.right_width;
priv->border.top_width = config->border.top_width;
priv->border.bottom_width = config->border.bottom_width;
priv->border.color = config->border.color;
priv->border.left_margin = config->border.left_margin;
priv->border.right_margin = config->border.right_margin;
priv->border.top_margin = config->border.top_margin;
priv->border.bottom_margin = config->border.bottom_margin;
priv->left.mods = malloc(config->left.count * sizeof(priv->left.mods[0]));
priv->left.exps = calloc(config->left.count, sizeof(priv->left.exps[0]));
priv->center.mods = malloc(config->center.count * sizeof(priv->center.mods[0]));
priv->center.exps = calloc(config->center.count, sizeof(priv->center.exps[0]));
priv->right.mods = malloc(config->right.count * sizeof(priv->right.mods[0]));
priv->right.exps = calloc(config->right.count, sizeof(priv->right.exps[0]));
priv->left.count = config->left.count;
priv->center.count = config->center.count;
priv->right.count = config->right.count;
priv->backend.data = backend_data;
priv->backend.iface = backend_iface;
for (size_t i = 0; i < priv->left.count; i++)
priv->left.mods[i] = config->left.mods[i];
for (size_t i = 0; i < priv->center.count; i++)
priv->center.mods[i] = config->center.mods[i];
for (size_t i = 0; i < priv->right.count; i++)
priv->right.mods[i] = config->right.mods[i];
struct bar *bar = calloc(1, sizeof(*bar));
bar->private = priv;
bar->run = &run;
bar->destroy = &destroy;
bar->refresh = &refresh;
bar->set_cursor = &set_cursor;
bar->output_name = &output_name;
for (size_t i = 0; i < priv->left.count; i++)
priv->left.mods[i]->bar = bar;
for (size_t i = 0; i < priv->center.count; i++)
priv->center.mods[i]->bar = bar;
for (size_t i = 0; i < priv->right.count; i++)
priv->right.mods[i]->bar = bar;
return bar;
}