yambar/bar/bar.c
2023-01-22 19:20:22 -05:00

673 lines
19 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))
/*
* Repeat a given action for all widths in a section
* and store the result in out.
*
* Note: out should first be set to zero or the action will
* also be preformed on the pre-given value.
*/
static void
accum_widths(
const struct section *s, const struct private *b,
int *out, int (*act)(int a, int b))
{
for (size_t i = 0; i < s->count; i++) {
struct exposable *e = s->exps[i];
if (e->width > 0)
*out = act(*out, b->left_spacing + e->width + b->right_spacing);
}
}
/*
* Repeat a given action for all heights in a section
* and store the result in out.
*
* Note: out should first be set to zero or the action will
* also be preformed on the pre-given value.
*/
static void
accum_heights(const struct section *s, const struct private *b,
int *out, int (*act)(int a, int b))
{
for (size_t i = 0; i < s->count; i++) {
struct exposable *e = s->exps[i];
if (e->height > 0)
*out = act(*out, b->top_spacing + e->height + b->bottom_spacing);
}
}
/* Add action */
static int
add (int a, int b)
{
return a + b;
}
/* Max action */
static int
larger(int a, int b)
{
return max(a, b);
}
/*
* Calculate total width of left/center/right 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;
accum_widths(&b->left, b, left, &add);
accum_widths(&b->center, b, center, &add);
accum_widths(&b->right, b, right, &add);
/* 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);
}
/*
* Calculate total height of left/center/right groups.
* Note: begin_expose() must have been called
*/
static void
calculate_heights (const struct private *b, int *top, int *middle, int *bottom)
{
*top = 0;
*middle = 0;
*bottom = 0;
accum_heights(&b->left, b, top, &add);
accum_heights(&b->center, b, middle, &add);
accum_heights(&b->right, b, bottom, &add);
/* No spacing on the edges (that's what the margins are for) */
if (*top > 0)
*top -= b->top_spacing + b->bottom_spacing;
if (*middle > 0)
*middle -= b->top_spacing + b->bottom_spacing;
if (*bottom > 0)
*bottom -= b->top_spacing + b->bottom_spacing;
assert(*top >= 0);
assert(*middle >= 0);
assert(*bottom >= 0);
}
/*
* Calculate the minimum width the bar needs to be to show all particles.
* This assumes the bar is at the left or right of screen.
* NOTE: begin_expose() must have been called
*/
static int
min_bar_width (const struct private *b)
{
int min = 0;
accum_widths(&b->left, b, &min, &larger);
accum_widths(&b->center, b, &min, &larger);
accum_widths(&b->right, b, &min, &larger);
return min;
}
/*
* Calculate the minimum height the bar needs to be to show all particles.
* This assumes the bar is at the top or bottom of the screen.
* NOTE: begin_expose() must have been called
*/
static int
min_bar_height (const struct private *b)
{
int min = 0;
accum_heights(&b->left, b, &min, &larger);
accum_heights(&b->center, b, &min, &larger);
accum_heights(&b->right, b, &min, &larger);
return min;
}
static void
begin_expose_mods(const struct section *s)
{
for (size_t i = 0; i < s->count; i++)
{
struct module *m = s->mods[i];
struct exposable *e = s->exps[i];
if (e != NULL)
e->destroy(e);
s->exps[i] = module_begin_expose(m);
assert(s->exps[i]->width >= 0 && s->exps[i]->height >= 0);
}
}
static void
bar_recalc_size(struct private *bar)
{
begin_expose_mods(&bar->left);
begin_expose_mods(&bar->center);
begin_expose_mods(&bar->right);
if (bar->height < 0){
if ((bar->location & (BAR_TOP | BAR_BOTTOM)) == bar->location)
bar->height_with_border =
min_bar_height(bar) + bar->border.top_width + bar->border.bottom_width + bar->top_margin + bar->bottom_margin;
else
bar->height_with_border = 0;
LOG_INFO("auto height: %d", bar->height_with_border);
} else
bar->height_with_border =
bar->height + bar->border.top_width + bar->border.bottom_width;
if (bar->width < 0){
if ((bar->location & (BAR_LEFT | BAR_RIGHT)) == bar->location)
bar->width_with_border =
min_bar_width(bar) + bar->border.left_width + bar->border.right_width + bar->left_margin + bar->right_margin;
else
bar->width_with_border = 0;
LOG_INFO("auto width: %d", bar->width_with_border);
}else
bar->width_with_border =
bar->width + bar->border.top_width + bar->border.bottom_width;
}
static bool
is_vertical (const struct bar *_bar)
{
const struct private *bar = _bar->private;
return bar->location == BAR_LEFT || bar->location == BAR_RIGHT;
}
static void
bar_expose_section (
const struct section *s, const struct private *b,
pixman_image_t *pix, int x, int y, bool vertical)
{
for (size_t i = 0; i < s->count; i++) {
const struct exposable *e = s->exps[i];
e->expose(e, pix, x, y, b->height);
if (vertical && e->height > 0)
y += b->top_spacing + e->height + b->bottom_spacing;
else if (!vertical && e->width > 0)
x += b->left_spacing + e->width + b->right_spacing;
}
}
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_with_border, 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_with_border - bar->border.right_width,
0, bar->border.right_width, bar->height_with_border},
/* Top */
{bar->border.left_width,
0,
bar->width_with_border - 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_with_border - bar->border.left_width - bar->border.right_width,
bar->border.bottom_width},
});
pixman_region32_t clip;
pixman_region32_init_rect(
&clip,
bar->border.left_width,
bar->border.top_width,
(bar->width_with_border -
bar->border.left_width - bar->border.right_width),
(bar->height_with_border -
bar->border.top_width - bar->border.bottom_width));
pixman_image_set_clip_region32(pix, &clip);
pixman_region32_fini(&clip);
int left_width, center_width, right_width;
calculate_widths(bar, &left_width, &center_width, &right_width);
int top_height, center_height, bottom_height;
calculate_heights(bar, &top_height, &center_height, &bottom_height);
bool virt = is_vertical(_bar);
int y = bar->border.top_width + bar->top_margin;
int x = bar->border.left_width + bar->left_margin;
bar_expose_section(&bar->left, bar, pix, x, y, virt);
if (virt)
y = (bar->height_with_border - center_height) / 2;
else
x = (bar->width_with_border - center_width) / 2;
bar_expose_section(&bar->center, bar, pix, x, y, virt);
if(virt)
y = bar->height_with_border - (
bottom_height +
bar->top_spacing +
bar->bottom_margin +
bar->border.bottom_width);
else
x = bar->width_with_border - (
right_width +
bar->left_spacing +
bar->right_margin +
bar->border.right_width);
bar_expose_section(&bar->right, bar, pix, x, y, virt);
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_recalc_size(bar);
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) {
const struct module *m = bar->left.mods[i];
LOG_ERR("module: LEFT #%zu (%s): non-zero exit value: %d",
i, m->description(m), 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) {
const struct module *m = bar->center.mods[i];
LOG_ERR("module: CENTER #%zu (%s): non-zero exit value: %d",
i, m->description(m), 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) {
const struct module *m = bar->right.mods[i];
LOG_ERR("module: RIGHT #%zu (%s): non-zero exit value: %d",
i, m->description(m), 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->width = config->width;
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->top_spacing = config->top_spacing;
priv->bottom_spacing = config->bottom_spacing;
priv->top_margin = config->top_margin;
priv->bottom_margin = config->bottom_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->is_vertical = &is_vertical;
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;
}