initial commit: wip

This commit is contained in:
Daniel Eklöf 2018-11-17 11:30:33 +01:00
commit 8bf8a398b9
30 changed files with 2921 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
bld/

55
CMakeLists.txt Normal file
View file

@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.9)
project(foobar C)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "-Wall -Werror ${CMAKE_C_FLAGS}")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(XCB REQUIRED xcb xcb-randr xcb-render)
pkg_check_modules(CAIRO REQUIRED cairo cairo-xcb)
pkg_check_modules(YAML REQUIRED yaml-0.1)
add_executable(foobar
bar.c bar.h
config.c config.h
font.c font.h
main.c
module.c module.h
particle.c particle.h
tag.c tag.h
xcb.c xcb.h
yml.c yml.h
particles/string.c particles/string.h
particles/list.c particles/list.h
modules/label.c modules/label.h
modules/clock.c modules/clock.h
modules/xwindow.c modules/xwindow.h
)
target_compile_definitions(foobar PRIVATE _GNU_SOURCE)
target_compile_options(foobar PRIVATE
${XCB_CFLAGS_OTHER}
${CAIRO_CFLAGS_OTHER}
${YAML_CFLAGS_OTHER}
)
target_include_directories(foobar PRIVATE
${XCB_INCLUDE_DIRS}
${CAIRO_INCLUDE_DIRS}
${YAML_INCLUDE_DIRS}
)
target_link_libraries(foobar
${CMAKE_THREAD_LIBS_INIT}
${XCB_LIBRARIES}
${CAIRO_LIBRARIES}
${YAML_LIBRARIES}
)

579
bar.c Normal file
View file

@ -0,0 +1,579 @@
#include "bar.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <threads.h>
#include <assert.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/xcb_ewmh.h>
#include <cairo.h>
#include <cairo-xcb.h>
#include "xcb.h"
struct private {
/* From bar_config */
int height;
int left_spacing, right_spacing;
int left_margin, right_margin;
struct rgba background;
struct {
int width;
struct rgba color;
} border;
struct {
struct module **mods;
size_t count;
} left;
struct {
struct module **mods;
size_t count;
} center;
struct {
struct module **mods;
size_t count;
} right;
/* Calculated run-time */
int x, y;
int width;
int height_with_border;
/* Resources */
xcb_connection_t *conn;
xcb_window_t win;
xcb_colormap_t colormap;
xcb_pixmap_t pixmap;
xcb_gc_t gc;
cairo_t *cairo;
};
static void
expose(const struct bar *_bar)
{
const struct private *bar = _bar->private;
double r, g, b, a;
r = bar->background.red;
g = bar->background.green;
b = bar->background.blue;
a = bar->background.alpha;
cairo_set_source_rgba(bar->cairo, r, g, b, a);
cairo_set_operator(bar->cairo, CAIRO_OPERATOR_SOURCE);
cairo_paint(bar->cairo);
if (bar->border.width > 0) {
/* TODO: actually use border width */
r = bar->border.color.red;
g = bar->border.color.green;
b = bar->border.color.blue;
a = bar->border.color.alpha;
cairo_set_line_width(bar->cairo, bar->border.width);
cairo_set_source_rgba(bar->cairo, r, g, b, a);
cairo_set_operator(bar->cairo, CAIRO_OPERATOR_OVER);
cairo_rectangle(bar->cairo, 0, 0, bar->width, bar->height_with_border);
cairo_stroke(bar->cairo);
}
int left_width = 0;
int center_width = 0;
int right_width = 0;
struct module_expose_context ctx_left[bar->left.count];
for (size_t i = 0; i < bar->left.count; i++) {
const struct module *m = bar->left.mods[i];
ctx_left[i] = m->begin_expose(m, bar->cairo);
left_width += bar->left_spacing + ctx_left[i].width + bar->right_spacing;
}
struct module_expose_context ctx_center[bar->center.count];
for (size_t i = 0; i < bar->center.count; i++) {
const struct module *m = bar->center.mods[i];
ctx_center[i] = m->begin_expose(m, bar->cairo);
center_width += bar->left_spacing + ctx_center[i].width + bar->right_spacing;
}
struct module_expose_context ctx_right[bar->right.count];
for (size_t i = 0; i < bar->right.count; i++) {
const struct module *m = bar->right.mods[i];
ctx_right[i] = m->begin_expose(m, bar->cairo);
right_width += bar->left_spacing + ctx_right[i].width + bar->right_spacing;
}
/* No spacing on the edges (that's what the margins are for) */
left_width -= bar->left_spacing + bar->right_spacing;
center_width -= bar->left_spacing + bar->right_spacing;
right_width -= bar->left_spacing + bar->right_spacing;
int y = bar->border.width;
int x = bar->border.width + bar->left_margin - bar->left_spacing;
for (size_t i = 0; i < bar->left.count; i++) {
const struct module *m = bar->left.mods[i];
m->expose(m, &ctx_left[i], bar->cairo, x + bar->left_spacing, y, bar->height);
x += bar->left_spacing + ctx_left[i].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 module *m = bar->center.mods[i];
m->expose(m, &ctx_center[i], bar->cairo, x + bar->left_spacing, y, bar->height);
x += bar->left_spacing + ctx_center[i].width + bar->right_spacing;
}
x = bar->width - (
right_width +
bar->left_spacing +
bar->right_margin +
bar->border.width);
for (size_t i = 0; i < bar->right.count; i++) {
const struct module *m = bar->right.mods[i];
m->expose(m, &ctx_right[i], bar->cairo, x + bar->left_spacing, y, bar->height);
x += bar->left_spacing + ctx_right[i].width + bar->right_spacing;
}
xcb_copy_area(bar->conn, bar->pixmap, bar->win, bar->gc,
0, 0, 0, 0, bar->width, bar->height_with_border);
for (size_t i = 0; i < bar->left.count; i++) {
const struct module *m = bar->left.mods[i];
m->end_expose(m, &ctx_left[i]);
}
for (size_t i = 0; i < bar->center.count; i++) {
const struct module *m = bar->center.mods[i];
m->end_expose(m, &ctx_center[i]);
}
for (size_t i = 0; i < bar->right.count; i++) {
const struct module *m = bar->right.mods[i];
m->end_expose(m, &ctx_right[i]);
}
}
static int
run(struct bar_run_context *run_ctx)
{
struct bar *_bar = run_ctx->bar;
struct private *bar = _bar->private;
/* TODO: a lot of this (up to mapping the window) could be done in bar_new() */
xcb_generic_error_t *e;
bar->conn = xcb_connect(NULL, NULL);
assert(bar->conn != NULL);
const xcb_setup_t *setup = xcb_get_setup(bar->conn);
xcb_screen_t *screen = xcb_setup_roots_iterator(setup).data;
xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply(
bar->conn,
xcb_randr_get_monitors(bar->conn, screen->root, 0),
&e);
assert(e == NULL);
const bool at_top = false;
bar->height_with_border = bar->height + 2 * bar->border.width;
/* Find monitor coordinates and width/height */
printf("Monitors:\n");
for (xcb_randr_monitor_info_iterator_t it =
xcb_randr_get_monitors_monitors_iterator(monitors);
it.rem > 0;
xcb_randr_monitor_info_next(&it))
{
const xcb_randr_monitor_info_t *mon = it.data;
char *name = get_atom_name(bar->conn, mon->name);
printf(" %s: %ux%u+%u+%u (%ux%umm)\n", name,
mon->width, mon->height, mon->x, mon->y,
mon->width_in_millimeters, mon->height_in_millimeters);
free(name);
if (!mon->primary)
continue;
bar->x = mon->x;
bar->y = mon->y;
bar->width = mon->width;
bar->y += at_top ? 0 : screen->height_in_pixels - bar->height_with_border;
break;
}
free(monitors);
/* Find a 32-bit visual (TODO: fallback to 24-bit) */
const uint8_t wanted_depth = 32;
const uint8_t wanted_class = 0;
uint8_t depth = 0;
xcb_visualtype_t *vis = NULL;
for (xcb_depth_iterator_t it = xcb_screen_allowed_depths_iterator(screen);
it.rem > 0;
xcb_depth_next(&it))
{
const xcb_depth_t *_depth = it.data;
if (!(wanted_depth == 0 || _depth->depth == wanted_depth))
continue;
for (xcb_visualtype_iterator_t vis_it =
xcb_depth_visuals_iterator(_depth);
vis_it.rem > 0;
xcb_visualtype_next(&vis_it))
{
xcb_visualtype_t *_vis = vis_it.data;
if (!(wanted_class == 0 || _vis->_class == wanted_class))
continue;
vis = _vis;
break;
}
if (vis != NULL) {
depth = _depth->depth;
break;
}
}
assert(depth == 32 || depth == 24);
assert(vis != NULL);
bar->colormap = xcb_generate_id(bar->conn);
xcb_create_colormap(bar->conn, 0, bar->colormap, screen->root, vis->visual_id);
bar->win = xcb_generate_id(bar->conn);
xcb_create_window(
bar->conn,
depth, bar->win, screen->root,
bar->x, bar->y, bar->width, bar->height_with_border,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id,
(XCB_CW_BACK_PIXEL |
XCB_CW_BORDER_PIXEL |
XCB_CW_EVENT_MASK |
XCB_CW_COLORMAP),
(const uint32_t []){
screen->black_pixel,
screen->white_pixel,
(XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_STRUCTURE_NOTIFY),
bar->colormap}
);
const char *title = "hello world";
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
strlen(title), title);
const xcb_atom_t _NET_WM_PID = get_atom(bar->conn, "_NET_WM_PID");
const xcb_atom_t _NET_WM_WINDOW_TYPE = get_atom(bar->conn, "_NET_WM_WINDOW_TYPE");
const xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK = get_atom(bar->conn, "_NET_WM_WINDOW_TYPE_DOCK");
const xcb_atom_t _NET_WM_STATE = get_atom(bar->conn, "_NET_WM_STATE");
const xcb_atom_t _NET_WM_STATE_ABOVE = get_atom(bar->conn, "_NET_WM_STATE_ABOVE");
const xcb_atom_t _NET_WM_STATE_STICKY = get_atom(bar->conn, "_NET_WM_STATE_STICKY");
const xcb_atom_t _NET_WM_DESKTOP = get_atom(bar->conn, "_NET_WM_DESKTOP");
const xcb_atom_t _NET_WM_STRUT = get_atom(bar->conn, "_NET_WM_STRUT");
const xcb_atom_t _NET_WM_STRUT_PARTIAL = get_atom(bar->conn, "_NET_WM_STRUT_PARTIAL");
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){getpid()});
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
_NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32,
1, (const uint32_t []){_NET_WM_WINDOW_TYPE_DOCK});
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
_NET_WM_STATE, XCB_ATOM_ATOM, 32,
2, (const uint32_t []){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY});
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
_NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t []){0xffffffff});
/* Always on top */
xcb_configure_window(
bar->conn, bar->win, XCB_CONFIG_WINDOW_STACK_MODE,
(const uint32_t []){XCB_STACK_MODE_ABOVE});
uint32_t top_strut, bottom_strut;
uint32_t top_pair[2], bottom_pair[2];
if (at_top) {
top_strut = bar->y + bar->height_with_border;
top_pair[0] = bar->x;
top_pair[1] = bar->x + bar->width - 1;
bottom_strut = 0;
bottom_pair[0] = bottom_pair[1] = 0;
} else {
bottom_strut = screen->height_in_pixels - bar->y;
bottom_pair[0] = bar->x;
bottom_pair[1] = bar->x + bar->width - 1;
top_strut = 0;
top_pair[0] = top_pair[1] = 0;
}
uint32_t strut[] = {
/* left/right/top/bottom */
0, 0,
top_strut,
bottom_strut,
/* start/end pairs for left/right/top/bottom */
0, 0,
0, 0,
top_pair[0], top_pair[1],
bottom_pair[0], bottom_pair[1],
};
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
_NET_WM_STRUT, XCB_ATOM_CARDINAL, 32,
4, strut);
xcb_change_property(
bar->conn,
XCB_PROP_MODE_REPLACE, bar->win,
_NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32,
12, strut);
bar->pixmap = xcb_generate_id(bar->conn);
xcb_create_pixmap(bar->conn, depth, bar->pixmap, bar->win,
bar->width, bar->height_with_border);
bar->gc = xcb_generate_id(bar->conn);
xcb_create_gc(bar->conn, bar->gc, bar->pixmap,
XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES,
(const uint32_t []){screen->white_pixel, 0});
cairo_surface_t *surface = cairo_xcb_surface_create(
bar->conn, bar->pixmap, vis, bar->width, bar->height_with_border);
bar->cairo = cairo_create(surface);
xcb_map_window(bar->conn, bar->win);
xcb_flush(bar->conn);
/* Start modules */
thrd_t thrd_left[bar->left.count];
thrd_t thrd_center[bar->center.count];
thrd_t thrd_right[bar->right.count];
struct module_run_context run_ctx_left[bar->left.count];
struct module_run_context run_ctx_center[bar->center.count];
struct module_run_context run_ctx_right[bar->right.count];
for (size_t i = 0; i < bar->left.count; i++) {
struct module_run_context *ctx = &run_ctx_left[i];
ctx->module = bar->left.mods[i];
ctx->abort_fd = run_ctx->abort_fd;
thrd_create(&thrd_left[i], (int (*)(void *))bar->left.mods[i]->run, ctx);
}
for (size_t i = 0; i < bar->center.count; i++) {
struct module_run_context *ctx = &run_ctx_center[i];
ctx->module = bar->center.mods[i];
ctx->abort_fd = run_ctx->abort_fd;
thrd_create(&thrd_center[i], (int (*)(void *))bar->center.mods[i]->run, ctx);
}
for (size_t i = 0; i < bar->right.count; i++) {
struct module_run_context *ctx = &run_ctx_right[i];
ctx->module = bar->right.mods[i];
ctx->abort_fd = run_ctx->abort_fd;
thrd_create(&thrd_right[i], (int (*)(void *))bar->right.mods[i]->run, ctx);
}
printf("modules started\n");
int fd = xcb_get_file_descriptor(bar->conn);
while (true) {
struct pollfd fds[] = {
{.fd = run_ctx->abort_fd, .events = POLLIN},
{.fd = fd, .events = POLLIN}
};
int ret = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
assert(ret == 1);
if (fds[0].revents && POLLIN)
break;
for (xcb_generic_event_t *e = xcb_wait_for_event(bar->conn);
e != NULL;
e = xcb_poll_for_event(bar->conn))
{
switch (XCB_EVENT_RESPONSE_TYPE(e)) {
case XCB_EXPOSE:
expose(_bar);
break;
case XCB_BUTTON_RELEASE:
case XCB_BUTTON_PRESS:
case XCB_MOTION_NOTIFY:
case XCB_DESTROY_NOTIFY:
case XCB_REPARENT_NOTIFY:
case XCB_CONFIGURE_NOTIFY:
case XCB_MAP_NOTIFY:
printf("unimplemented event\n");
break;
default:
printf("unsupported event: %d\n", XCB_EVENT_RESPONSE_TYPE(e));
break;
}
free(e);
xcb_flush(bar->conn);
}
}
/* Wait for modules to terminate */
int mod_ret;
for (size_t i = 0; i < bar->left.count; i++)
thrd_join(thrd_left[i], &mod_ret);
for (size_t i = 0; i < bar->center.count; i++)
thrd_join(thrd_center[i], &mod_ret);
for (size_t i = 0; i < bar->right.count; i++)
thrd_join(thrd_right[i], &mod_ret);
printf("modules joined\n");
cairo_destroy(bar->cairo);
cairo_surface_destroy(surface);
cairo_debug_reset_static_data();
xcb_free_gc(bar->conn, bar->gc);
xcb_free_pixmap(bar->conn, bar->pixmap);
xcb_destroy_window(bar->conn, bar->win);
xcb_free_colormap(bar->conn, bar->colormap);
xcb_flush(bar->conn);
xcb_disconnect(bar->conn);
printf("bar exiting\n");
return 0;
}
static void
refresh(const struct bar *bar)
{
const struct private *b = bar->private;
/* Send an event to handle refresh from main thread */
/* Note: docs say that all X11 events are 32 bytes, reglardless of
* the size of the event structure */
xcb_expose_event_t *evt = calloc(32, 1);
*evt = (xcb_expose_event_t){
.response_type = XCB_EXPOSE,
.window = b->win,
.x = 0,
.y = 0,
.width = b->width,
.height = b->height,
.count = 1
};
xcb_send_event(b->conn, false, b->win, XCB_EVENT_MASK_EXPOSURE, (char *)evt);
xcb_flush(b->conn);
free(evt);
}
static void
destroy(struct bar *bar)
{
struct private *b = bar->private;
for (size_t i = 0; i < b->left.count; i++)
b->left.mods[i]->destroy(b->left.mods[i]);
for (size_t i = 0; i < b->center.count; i++)
b->center.mods[i]->destroy(b->center.mods[i]);
for (size_t i = 0; i < b->right.count; i++)
b->right.mods[i]->destroy(b->right.mods[i]);
free(b->left.mods);
free(b->center.mods);
free(b->right.mods);
free(bar->private);
free(bar);
}
struct bar *
bar_new(const struct bar_config *config)
{
struct private *priv = malloc(sizeof(*priv));
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->border.width = config->border.width;
priv->border.color = config->border.color;
priv->left.mods = malloc(config->left.count * sizeof(priv->left.mods[0]));
priv->center.mods = malloc(config->center.count * sizeof(priv->center.mods[0]));
priv->right.mods = malloc(config->right.count * sizeof(priv->right.mods[0]));
priv->left.count = config->left.count;
priv->center.count = config->center.count;
priv->right.count = config->right.count;
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 = malloc(sizeof(*bar));
bar->private = priv;
bar->run = &run;
bar->destroy = &destroy;
bar->refresh = &refresh;
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;
}

44
bar.h Normal file
View file

@ -0,0 +1,44 @@
#pragma once
#include "color.h"
#include "module.h"
struct bar;
struct bar_run_context {
struct bar *bar;
int abort_fd;
};
struct bar {
void *private;
int (*run)(struct bar_run_context *ctx);
void (*destroy)(struct bar *bar);
void (*refresh)(const struct bar *bar);
};
struct bar_config {
int height;
int left_spacing, right_spacing;
int left_margin, right_margin;
struct rgba background;
struct {
int width;
struct rgba color;
} border;
struct {
struct module **mods;
size_t count;
} left;
struct {
struct module **mods;
size_t count;
} center;
struct {
struct module **mods;
size_t count;
} right;
};
struct bar *bar_new(const struct bar_config *config);

8
color.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
struct rgba {
double red;
double green;
double blue;
double alpha;
};

308
config.c Normal file
View file

@ -0,0 +1,308 @@
#include "config.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "color.h"
#include "particle.h"
#include "particles/string.h"
#include "particles/list.h"
#include "module.h"
#include "modules/label.h"
#include "modules/clock.h"
#include "modules/xwindow.h"
static uint8_t
hex_nibble(char hex)
{
assert((hex >= '0' && hex <= '9') ||
(hex >= 'a' && hex <= 'f') ||
(hex >= 'A' && hex <= 'F'));
if (hex >= '0' && hex <= '9')
return hex - '0';
else if (hex >= 'a' && hex <= 'f')
return hex - 'a' + 10;
else
return hex - 'A' + 10;
}
static uint8_t
hex_byte(const char hex[2])
{
uint8_t upper = hex_nibble(hex[0]);
uint8_t lower = hex_nibble(hex[1]);
return upper << 4 | lower;
}
static struct rgba
color_from_hexstr(const char *hex)
{
assert(strlen(hex) == 8);
uint8_t red = hex_byte(&hex[0]);
uint8_t green = hex_byte(&hex[2]);
uint8_t blue = hex_byte(&hex[4]);
uint8_t alpha = hex_byte(&hex[6]);
struct rgba rgba = {
(double)red / 255.0,
(double)green / 255.0,
(double)blue / 255.0,
(double)alpha / 255.0
};
assert(rgba.red >= 0.0 && rgba.red <= 1.0);
assert(rgba.green >= 0.0 && rgba.green <= 1.0);
assert(rgba.blue >= 0.0 && rgba.blue <= 1.0);
assert(rgba.alpha >= 0.0 && rgba.alpha <= 1.0);
return rgba;
}
static struct font *
font_from_config(const struct yml_node *node)
{
const struct yml_node *family = yml_get_value(node, "family");
const struct yml_node *size = yml_get_value(node, "size");
const struct yml_node *italic = yml_get_value(node, "italic");
const struct yml_node *bold = yml_get_value(node, "bold");
const struct yml_node *y_offset = yml_get_value(node, "y_offset");
return font_new(
family != NULL ? yml_value_as_string(family) : "monospace",
size != NULL ? yml_value_as_int(size) : 12,
italic != NULL ? yml_value_as_bool(italic) : false,
bold != NULL ? yml_value_as_bool(bold) : false,
y_offset != NULL ? yml_value_as_int(y_offset) : 0);
}
static struct particle *
particle_string_from_config(const struct yml_node *node, const struct font *parent_font)
{
assert(yml_is_dict(node));
const struct yml_node *text_node = yml_get_value(node, "text");
const struct yml_node *font_node = yml_get_value(node, "font");
const struct yml_node *foreground_node = yml_get_value(node, "foreground");
const struct yml_node *margin_node = yml_get_value(node, "margin");
const struct yml_node *left_margin_node = yml_get_value(node, "left_margin");
const struct yml_node *right_margin_node = yml_get_value(node, "right_margin");
/* TODO: inherit values? At least color... */
struct rgba foreground = {1.0, 1.0, 1.0, 1.0};
int left_margin = 0;
int right_margin = 0;
struct font *font = NULL;
if (font_node != NULL)
font = font_from_config(font_node);
else
font = font_clone(parent_font);
if (foreground_node != NULL)
foreground = color_from_hexstr(yml_value_as_string(foreground_node));
if (margin_node != NULL)
left_margin = right_margin = yml_value_as_int(margin_node);
if (left_margin_node != NULL)
left_margin = yml_value_as_int(left_margin_node);
if (right_margin_node != NULL)
right_margin = yml_value_as_int(right_margin_node);
assert(text_node != NULL);
return particle_string_new(
yml_value_as_string(text_node), font, foreground, left_margin, right_margin);
}
static struct particle * particle_from_config(
const struct yml_node *node, const struct font *parent_font);
static struct particle *
particle_list_from_config(const struct yml_node *node,
const struct font *parent_font)
{
const struct yml_node *items_node = yml_get_value(node, "items");
const struct yml_node *margin_node = yml_get_value(node, "margin");
const struct yml_node *left_margin_node = yml_get_value(node, "left_margin");
const struct yml_node *right_margin_node = yml_get_value(node, "right_margin");
const struct yml_node *spacing_node = yml_get_value(node, "spacing");
const struct yml_node *left_spacing_node = yml_get_value(node, "left_spacing");
const struct yml_node *right_spacing_node = yml_get_value(node, "right_spacing");
int left_margin = 0;
int right_margin = 0;
int left_spacing = 0;
int right_spacing = 0;
if (margin_node != NULL)
left_margin = right_margin = yml_value_as_int(margin_node);
if (left_margin_node != NULL)
left_margin = yml_value_as_int(left_margin_node);
if (right_margin_node != NULL)
right_margin = yml_value_as_int(right_margin_node);
if (spacing_node != NULL)
left_spacing = right_spacing = yml_value_as_int(spacing_node);
if (left_spacing_node != NULL)
left_spacing = yml_value_as_int(left_spacing_node);
if (right_spacing_node != NULL)
right_spacing = yml_value_as_int(right_spacing_node);
size_t count = yml_list_length(items_node);
struct particle **parts = calloc(count, sizeof(*parts));
size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(items_node);
it.node != NULL;
yml_list_next(&it), idx++)
{
parts[idx] = particle_from_config(it.node, parent_font);
}
struct particle *list = particle_list_new(
parts, count, left_spacing, right_spacing, left_margin, right_margin);
free(parts);
return list;
}
static struct particle *
particle_from_config(const struct yml_node *node, const struct font *parent_font)
{
assert(yml_is_dict(node));
assert(yml_dict_length(node) == 1);
struct yml_dict_iter pair = yml_dict_iter(node);
const char *type = yml_value_as_string(pair.key);
if (strcmp(type, "string") == 0)
return particle_string_from_config(pair.value, parent_font);
else if (strcmp(type, "list") == 0)
return particle_list_from_config(pair.value, parent_font);
else
assert(false);
}
struct bar *
conf_to_bar(const struct yml_node *bar)
{
struct bar_config conf = {0};
/* Create a default font */
struct font *font = font_new("sans", 12, false, false, 0);
const struct yml_node *height = yml_get_value(bar, "height");
const struct yml_node *background = yml_get_value(bar, "background");
const struct yml_node *spacing = yml_get_value(bar, "spacing");
const struct yml_node *left_spacing = yml_get_value(bar, "left_spacing");
const struct yml_node *right_spacing = yml_get_value(bar, "right_spacing");
const struct yml_node *margin = yml_get_value(bar, "margin");
const struct yml_node *left_margin = yml_get_value(bar, "left_margin");
const struct yml_node *right_margin = yml_get_value(bar, "right_margin");
const struct yml_node *border = yml_get_value(bar, "border");
const struct yml_node *font_node = yml_get_value(bar, "font");
const struct yml_node *left = yml_get_value(bar, "left");
const struct yml_node *center = yml_get_value(bar, "center");
const struct yml_node *right = yml_get_value(bar, "right");
if (height != NULL)
conf.height = yml_value_as_int(height);
if (background != NULL)
conf.background = color_from_hexstr(yml_value_as_string(background));
if (spacing != NULL)
conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing);
if (left_spacing != NULL)
conf.left_spacing = yml_value_as_int(left_spacing);
if (right_spacing != NULL)
conf.right_spacing = yml_value_as_int(right_spacing);
if (margin != NULL)
conf.left_margin = conf.right_margin = yml_value_as_int(margin);
if (left_margin != NULL)
conf.left_margin = yml_value_as_int(left_margin);
if (right_margin != NULL)
conf.right_margin = yml_value_as_int(right_margin);
if (border != NULL) {
assert(yml_is_dict(border));
const struct yml_node *width = yml_get_value(border, "width");
const struct yml_node *color = yml_get_value(border, "color");
if (width != NULL)
conf.border.width = yml_value_as_int(width);
if (color != NULL)
conf.border.color = color_from_hexstr(yml_value_as_string(color));
}
if (font_node != NULL) {
font_destroy(font);
font = font_from_config(font_node);
}
for (size_t i = 0; i < 3; i++) {
const struct yml_node *node = i == 0 ? left : i == 1 ? center : right;
if (node != NULL) {
const size_t count = yml_list_length(node);
struct module **mods = calloc(count, sizeof(*mods));
size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(node);
it.node != NULL;
yml_list_next(&it), idx++)
{
const struct yml_node *n = yml_get_value(it.node, "module");
assert(n != NULL);
const struct yml_node *c = yml_get_value(it.node, "content");
assert(c != NULL);
const char *mod_name = yml_value_as_string(n);
struct particle *content = particle_from_config(c, font);
if (strcmp(mod_name, "label") == 0)
mods[idx] = module_label(content);
else if (strcmp(mod_name, "clock") == 0)
mods[idx] = module_clock(content);
else if (strcmp(mod_name, "xwindow") == 0)
mods[idx] = module_xwindow(content);
else
assert(false);
}
if (i == 0) {
conf.left.mods = mods;
conf.left.count = count;
} else if (i == 1) {
conf.center.mods = mods;
conf.center.count = count;
} else {
conf.right.mods = mods;
conf.right.count = count;
}
}
}
struct bar *ret = bar_new(&conf);
free(conf.left.mods);
free(conf.center.mods);
free(conf.right.mods);
font_destroy(font);
return ret;
}

7
config.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include "bar.h"
#include "yml.h"
#include "font.h"
struct bar *conf_to_bar(const struct yml_node *bar);

92
font.c Normal file
View file

@ -0,0 +1,92 @@
#include "font.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
struct font {
char *face;
int size;
bool italic;
bool bold;
int y_offset;
cairo_font_options_t *cairo_font_options;
};
struct font *
font_new(const char *face, int size, bool italic, bool bold, int y_offset)
{
struct font *font = malloc(sizeof(*font));
font->face = strdup(face);
font->size = size;
font->italic = italic;
font->bold = bold;
font->y_offset = y_offset;
font->cairo_font_options = cairo_font_options_create();
cairo_font_options_set_antialias(
font->cairo_font_options, CAIRO_ANTIALIAS_DEFAULT);
//antialias ? CAIRO_ANTIALIAS_SUBPIXEL : CAIRO_ANTIALIAS_NONE);
return font;
}
struct font *
font_clone(const struct font *font)
{
return font_new(font->face, font->size, font->italic, font->bold, font->y_offset);
}
void
font_destroy(struct font *font)
{
cairo_font_options_destroy(font->cairo_font_options);
free(font->face);
free(font);
}
const char *
font_face(const struct font *font)
{
return font->face;
}
int
font_size(const struct font *font)
{
return font->size;
}
bool
font_is_italic(const struct font *font)
{
return font->italic;
}
bool
font_is_bold(const struct font *font)
{
return font->bold;
}
int
font_y_offset(const struct font *font)
{
return font->y_offset;
}
cairo_scaled_font_t *
font_use_in_cairo(const struct font *font, cairo_t *cr)
{
cairo_font_slant_t slant = font->italic
? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL;
cairo_font_weight_t weight = font->bold
? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL;
cairo_select_font_face(cr, font->face, slant, weight);
cairo_set_font_size(cr, font->size);
cairo_set_font_options(cr, font->cairo_font_options);
return cairo_get_scaled_font(cr);
}

22
font.h Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <stdbool.h>
#include <cairo.h>
#include "color.h"
struct font;
struct font *font_new(
const char *face, int size, bool italic, bool bold, int y_offset);
struct font *font_clone(const struct font *font);
void font_destroy(struct font *font);
const char *font_face(const struct font *font);
int font_size(const struct font *font);
bool font_is_italic(const struct font *font);
bool font_is_bold(const struct font *font);
int font_y_offset(const struct font *font);
cairo_scaled_font_t *font_use_in_cairo(const struct font *font, cairo_t *cr);

132
main.c Normal file
View file

@ -0,0 +1,132 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#include <threads.h>
#include <sys/eventfd.h>
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include <xcb/render.h>
#include "bar.h"
#include "config.h"
#include "yml.h"
static volatile sig_atomic_t aborted = 0;
static void
signal_handler(int signo)
{
aborted = 1;
}
int
main(int argc, const char *const *argv)
{
FILE *conf_file = fopen("config.yml", "r");
assert(conf_file != NULL);
struct yml_node *conf = yml_load(conf_file);
fclose(conf_file);
xcb_connection_t *conn = xcb_connect(NULL, NULL);
assert(conn != NULL);
const xcb_setup_t *setup = xcb_get_setup(conn);
/* Vendor string */
int length = xcb_setup_vendor_length(setup);
char *vendor = malloc(length + 1);
memcpy(vendor, xcb_setup_vendor(setup), length);
vendor[length] = '\0';
/* Vendor release number */
unsigned release = setup->release_number;
unsigned major = release / 10000000; release %= 10000000;
unsigned minor = release / 100000; release %= 100000;
unsigned patch = release / 1000;
printf("%s %u.%u.%u (protocol: %u.%u)\n", vendor,
major, minor, patch,
setup->protocol_major_version,
setup->protocol_minor_version);
free(vendor);
const xcb_query_extension_reply_t *randr =
xcb_get_extension_data(conn, &xcb_randr_id);
assert(randr->present);
const xcb_query_extension_reply_t *render =
xcb_get_extension_data(conn, &xcb_render_id);
assert(render->present);
xcb_randr_query_version_cookie_t randr_cookie =
xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION,
XCB_RANDR_MINOR_VERSION);
xcb_render_query_version_cookie_t render_cookie =
xcb_render_query_version(conn, XCB_RENDER_MAJOR_VERSION,
XCB_RENDER_MINOR_VERSION);
xcb_generic_error_t *e;
xcb_randr_query_version_reply_t *randr_version =
xcb_randr_query_version_reply(conn, randr_cookie, &e);
assert(e == NULL);
xcb_render_query_version_reply_t *render_version =
xcb_render_query_version_reply(conn, render_cookie, &e);
assert(e == NULL);
xcb_flush(conn);
printf(" RANDR: %u.%u\n"
" RENDER: %u.%u\n",
randr_version->major_version,
randr_version->minor_version,
render_version->major_version,
render_version->minor_version);
free(randr_version);
free(render_version);
xcb_disconnect(conn);
const struct sigaction sa = {.sa_handler = &signal_handler};
sigaction(SIGINT, &sa, NULL);
int abort_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
assert(abort_fd >= 0);
struct bar *bar = conf_to_bar(yml_get_value(conf, "bar"));
#if 1
struct bar_run_context bar_ctx = {
.bar = bar,
.abort_fd = abort_fd,
};
thrd_t bar_thread;
thrd_create(&bar_thread, (int (*)(void *))bar->run, &bar_ctx);
while (!aborted) {
sleep(999999999);
}
/* Signal abort to all workers */
write(abort_fd, &(uint64_t){1}, sizeof(uint64_t));
int res;
thrd_join(bar_thread, &res);
#endif
bar->destroy(bar);
yml_destroy(conf);
close(abort_fd);
return 0;
}

27
module.c Normal file
View file

@ -0,0 +1,27 @@
#include "module.h"
#include <stdlib.h>
struct module_expose_context
module_default_begin_expose(const struct module *mod, cairo_t *cr)
{
struct exposable *e = mod->content(mod);
return (struct module_expose_context){
.exposable = e,
.width = e->begin_expose(e, cr),
};
}
void
module_default_expose(const struct module *mod,
const struct module_expose_context *ctx, cairo_t *cr,
int x, int y, int height)
{
ctx->exposable->expose(ctx->exposable, cr, x, y, height);
}
void
module_default_end_expose(const struct module *mod,
struct module_expose_context *ctx)
{
ctx->exposable->destroy(ctx->exposable);
}

46
module.h Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <cairo.h>
#include "particle.h"
#include "tag.h"
struct bar;
struct module;
struct module_run_context {
struct module *module;
int abort_fd;
};
struct module_expose_context {
struct exposable *exposable;
int width;
};
struct module {
const struct bar *bar;
void *private;
int (*run)(struct module_run_context *ctx);
void (*destroy)(struct module *module);
struct exposable *(*content)(const struct module *mod);
struct module_expose_context (*begin_expose)(const struct module *mod, cairo_t *cr);
void (*expose)(const struct module *mod,
const struct module_expose_context *ctx,
cairo_t *cr, int x, int y, int height);
void (*end_expose)(const struct module *mod, struct module_expose_context *ctx);
};
struct module_expose_context module_default_begin_expose(
const struct module *mod, cairo_t *cr);
void module_default_expose(
const struct module *mod,
const struct module_expose_context *ctx, cairo_t *cr,
int x, int y, int height);
void module_default_end_expose(
const struct module *mod, struct module_expose_context *ctx);

90
modules/clock.c Normal file
View file

@ -0,0 +1,90 @@
#include "clock.h"
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <poll.h>
#include "../bar.h"
struct private {
struct particle *label;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->label->destroy(m->label);
free(m);
free(mod);
}
static struct exposable *
content(const struct module *mod)
{
const struct private *m = mod->private;
time_t t = time(NULL);
struct tm *tm = localtime(&t);
char time_str[1024];
strftime(time_str, sizeof(time_str), "%H:%M", tm);
char date_str[1024];
strftime(date_str, sizeof(date_str), "%e %b", tm);
struct tag_set tags = {
.tags = (struct tag *[]){tag_new_string("time", time_str),
tag_new_string("date", date_str)},
.count = 2,
};
struct exposable *exposable = m->label->instantiate(m->label, &tags);
tag_set_destroy(&tags);
return exposable;
}
static int
run(struct module_run_context *ctx)
{
const struct bar *bar = ctx->module->bar;
while (true) {
time_t now = time(NULL);
time_t now_no_secs = now / 60 * 60;
assert(now_no_secs % 60 == 0);
time_t next_min = now_no_secs + 60;
time_t timeout = next_min - now;
assert(timeout >= 0 && timeout <= 60);
struct pollfd fds[] = {{.fd = ctx->abort_fd, .events = POLLIN}};
poll(fds, 1, timeout * 1000);
if (fds[0].revents & POLLIN)
break;
bar->refresh(bar);
}
return 0;
}
struct module *
module_clock(struct particle *label)
{
struct private *m = malloc(sizeof(*m));
m->label = label;
struct module *mod = malloc(sizeof(*mod));
mod->bar = NULL;
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
mod->begin_expose = &module_default_begin_expose;
mod->expose = &module_default_expose;
mod->end_expose = &module_default_end_expose;
return mod;
}

6
modules/clock.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "../module.h"
#include "../particle.h"
struct module *module_clock(struct particle *label);

51
modules/label.c Normal file
View file

@ -0,0 +1,51 @@
#include "label.h"
#include <stdlib.h>
#include <assert.h>
#include <poll.h>
struct private {
struct particle *label;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->label->destroy(m->label);
free(m);
free(mod);
}
static struct exposable *
content(const struct module *mod)
{
const struct private *m = mod->private;
return m->label->instantiate(m->label, NULL);
}
static int
run(struct module_run_context *ctx)
{
return 0;
}
struct module *
module_label(struct particle *label)
{
struct private *m = malloc(sizeof(*m));
m->label = label;
struct module *mod = malloc(sizeof(*mod));
mod->bar = NULL;
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
mod->begin_expose = &module_default_begin_expose;
mod->expose = &module_default_expose;
mod->end_expose = &module_default_end_expose;
return mod;
}

6
modules/label.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "../module.h"
#include "../particle.h"
struct module *module_label(struct particle *label);

336
modules/xwindow.c Normal file
View file

@ -0,0 +1,336 @@
#include "xwindow.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <threads.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#include "../bar.h"
#include "../xcb.h"
static bool globals_inited = false;
xcb_atom_t UTF8_STRING;
xcb_atom_t _NET_ACTIVE_WINDOW;
xcb_atom_t _NET_CURRENT_DESKTOP;
xcb_atom_t _NET_WM_VISIBLE_NAME;
xcb_atom_t _NET_WM_NAME;
xcb_atom_t _NET_WM_PID;
struct private {
/* Accessed from bar thread only */
struct particle *label;
/* Accessed from both our thread, and the bar thread */
mtx_t lock;
char *application;
char *title;
/* Accessed from our thread only */
xcb_connection_t *conn;
xcb_window_t root_win;
xcb_window_t monitor_win;
xcb_window_t active_win;
};
static void
init_globals(void)
{
if (globals_inited)
return;
xcb_connection_t *conn = xcb_connect(NULL, NULL);
assert(conn != NULL);
UTF8_STRING = get_atom(conn, "UTF8_STRING");
_NET_ACTIVE_WINDOW = get_atom(conn, "_NET_ACTIVE_WINDOW");
_NET_CURRENT_DESKTOP = get_atom(conn, "_NET_CURRENT_DESKTOP");
_NET_WM_VISIBLE_NAME = get_atom(conn, "_NET_WM_VISIBLE_NAME");
_NET_WM_NAME = get_atom(conn, "_NET_WM_NAME");
_NET_WM_PID = get_atom(conn, "_NET_WM_PID");
globals_inited = true;
xcb_disconnect(conn);
}
static void
update_active_window(struct private *m)
{
if (m->active_win != 0) {
xcb_change_window_attributes(
m->conn, m->active_win, XCB_CW_EVENT_MASK,
(const uint32_t []){XCB_EVENT_MASK_NO_EVENT});
m->active_win = 0;
}
xcb_get_property_cookie_t c = xcb_get_property(
m->conn, 0, m->root_win, _NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 0, 32);
xcb_generic_error_t *e;
xcb_get_property_reply_t *r = xcb_get_property_reply(m->conn, c, &e);
if (e != NULL) {
free(e);
free(r);
return;
}
assert(sizeof(m->active_win) == xcb_get_property_value_length(r));
memcpy(&m->active_win, xcb_get_property_value(r), sizeof(m->active_win));
free(r);
if (m->active_win != 0) {
xcb_change_window_attributes(
m->conn, m->active_win, XCB_CW_EVENT_MASK,
(const uint32_t []){XCB_EVENT_MASK_PROPERTY_CHANGE});
}
}
static void
update_application(struct private *m)
{
mtx_lock(&m->lock);
free(m->application);
m->application = NULL;
mtx_unlock(&m->lock);
if (m->active_win == 0)
return;
xcb_get_property_cookie_t c = xcb_get_property(
m->conn, 0, m->active_win, _NET_WM_PID, XCB_ATOM_CARDINAL, 0, 32);
xcb_generic_error_t *e;
xcb_get_property_reply_t *r = xcb_get_property_reply(m->conn, c, &e);
if (e != NULL) {
free(e);
free(r);
return;
}
uint32_t pid;
assert(xcb_get_property_value_length(r) == sizeof(pid));
memcpy(&pid, xcb_get_property_value(r), sizeof(pid));
free(r);
char path[1024];
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
int fd = open(path, O_RDONLY);
if (fd == -1)
return;
char cmd[1024] = {0};
ssize_t bytes = read(fd, cmd, sizeof(cmd) - 1);
close(fd);
if (bytes == -1)
return;
mtx_lock(&m->lock);
m->application = strdup(basename(cmd));
mtx_unlock(&m->lock);
}
static void
update_title(struct private *m)
{
mtx_lock(&m->lock);
free(m->title);
m->title = NULL;
mtx_unlock(&m->lock);
if (m->active_win == 0)
return;
xcb_get_property_cookie_t c1 = xcb_get_property(
m->conn, 0, m->active_win, _NET_WM_VISIBLE_NAME, UTF8_STRING, 0, 1000);
xcb_get_property_cookie_t c2 = xcb_get_property(
m->conn, 0, m->active_win, _NET_WM_NAME, UTF8_STRING, 0, 1000);
xcb_get_property_cookie_t c3 = xcb_get_property(
m->conn, 0, m->active_win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1000);
xcb_generic_error_t *e1, *e2, *e3;
xcb_get_property_reply_t *r1 = xcb_get_property_reply(m->conn, c1, &e1);
xcb_get_property_reply_t *r2 = xcb_get_property_reply(m->conn, c2, &e2);
xcb_get_property_reply_t *r3 = xcb_get_property_reply(m->conn, c3, &e3);
const char *title;
int title_len;
if (e1 == NULL && xcb_get_property_value_length(r1) > 0) {
title = xcb_get_property_value(r1);
title_len = xcb_get_property_value_length(r1);
} else if (e2 == NULL && xcb_get_property_value_length(r2) > 0) {
title = xcb_get_property_value(r2);
title_len = xcb_get_property_value_length(r2);
} else if (e3 == NULL && xcb_get_property_value_length(r3) > 0) {
title = xcb_get_property_value(r3);
title_len = xcb_get_property_value_length(r3);
} else {
title = NULL;
title_len = 0;
}
if (title_len > 0) {
mtx_lock(&m->lock);
m->title = malloc(title_len + 1);
memcpy(m->title, title, title_len);
m->title[title_len] = '\0';
mtx_unlock(&m->lock);
}
free(e1);
free(e2);
free(e3);
free(r1);
free(r2);
free(r3);
}
static int
run(struct module_run_context *ctx)
{
struct module *mod = ctx->module;
struct private *m = mod->private;
m->conn = xcb_connect(NULL, NULL);
assert(m->conn != NULL);
const xcb_setup_t *setup = xcb_get_setup(m->conn);
xcb_screen_t *screen = xcb_setup_roots_iterator(setup).data;
m->root_win = screen->root;
/* Need a window(?) to be able to process events */
m->monitor_win = xcb_generate_id(m->conn);
xcb_create_window(m->conn, screen->root_depth, m->monitor_win, screen->root,
-1, -1, 1, 1,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual,
XCB_CW_OVERRIDE_REDIRECT, (const uint32_t []){1});
xcb_map_window(m->conn, m->monitor_win);
/* Register for property changes on root window. This allows us to
* catch e.g. window switches etc */
xcb_change_window_attributes(
m->conn, screen->root, XCB_CW_EVENT_MASK,
(const uint32_t []){XCB_EVENT_MASK_PROPERTY_CHANGE});
xcb_flush(m->conn);
update_active_window(m);
update_application(m);
update_title(m);
mod->bar->refresh(mod->bar);
int xcb_fd = xcb_get_file_descriptor(m->conn);
while (true) {
struct pollfd fds[] = {{.fd = ctx->abort_fd, .events = POLLIN},
{.fd = xcb_fd, .events = POLLIN}};
poll(fds, 2, -1);
if (fds[0].revents & POLLIN)
break;
for (xcb_generic_event_t *_e = xcb_wait_for_event(m->conn);
_e != NULL;
_e = xcb_poll_for_event(m->conn))
{
switch (XCB_EVENT_RESPONSE_TYPE(_e)) {
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)_e;
if (e->atom == _NET_ACTIVE_WINDOW ||
e->atom == _NET_CURRENT_DESKTOP)
{
/* Active desktop and/or window changed */
update_active_window(m);
update_application(m);
update_title(m);
mod->bar->refresh(mod->bar);
} else if (e->atom == _NET_WM_VISIBLE_NAME ||
e->atom == _NET_WM_NAME ||
e->atom == XCB_ATOM_WM_NAME)
{
assert(e->window == m->active_win);
update_title(m);
mod->bar->refresh(mod->bar);
}
break;
}
case 0: break; /* error */
}
free(_e);
}
}
xcb_destroy_window(m->conn, m->monitor_win);
xcb_disconnect(m->conn);
return 0;
}
static struct exposable *
content(const struct module *mod)
{
struct private *m = mod->private;
mtx_lock(&m->lock);
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string("application", m->application ? m->application : ""),
tag_new_string("title", m->title ? m->title : "")},
.count = 2,
};
mtx_unlock(&m->lock);
struct exposable *exposable = m->label->instantiate(m->label, &tags);
tag_set_destroy(&tags);
return exposable;
}
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->label->destroy(m->label);
mtx_destroy(&m->lock);
free(m->application);
free(m->title);
free(m);
free(mod);
}
struct module *
module_xwindow(struct particle *label)
{
init_globals();
struct private *m = calloc(1, sizeof(*m));
m->label = label;
mtx_init(&m->lock, mtx_plain);
struct module *mod = malloc(sizeof(*mod));
mod->bar = NULL;
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
mod->begin_expose = &module_default_begin_expose;
mod->expose = &module_default_expose;
mod->end_expose = &module_default_end_expose;
return mod;
}

6
modules/xwindow.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "../module.h"
#include "../particle.h"
struct module *module_xwindow(struct particle *label);

12
particle.c Normal file
View file

@ -0,0 +1,12 @@
#include "particle.h"
#include <stdlib.h>
struct particle *
particle_common_new(int left_margin, int right_margin)
{
struct particle *p = malloc(sizeof(*p));
p->parent = NULL;
p->left_margin = left_margin;
p->right_margin = right_margin;
return p;
}

32
particle.h Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <cairo.h>
#include "color.h"
#include "font.h"
#include "tag.h"
struct exposable;
struct particle {
struct particle *parent;
void *private;
int left_margin, right_margin;
void (*destroy)(struct particle *particle);
struct exposable *(*instantiate)(const struct particle *particle,
const struct tag_set *tags);
};
struct exposable {
const struct particle *particle;
void *private;
void (*destroy)(struct exposable *exposable);
int (*begin_expose)(const struct exposable *exposable, cairo_t *cr);
void (*expose)(const struct exposable *exposable, cairo_t *cr,
int x, int y, int height);
};
struct particle *particle_common_new(int left_margin, int right_margin);

128
particles/list.c Normal file
View file

@ -0,0 +1,128 @@
#include "list.h"
#include <stdlib.h>
struct particle_private {
struct particle **particles;
size_t count;
int left_spacing, right_spacing;
};
struct exposable_private {
struct exposable **exposables;
int *widths;
size_t count;
int left_spacing, right_spacing;
};
static int
begin_expose(const struct exposable *exposable, cairo_t *cr)
{
const struct exposable_private *e = exposable->private;
int width = exposable->particle->left_margin;
for (size_t i = 0; i < e->count; i++) {
struct exposable *ee = e->exposables[i];
e->widths[i] = ee->begin_expose(ee, cr);
width += e->left_spacing + e->widths[i] + e->right_spacing;
}
width -= e->left_spacing + e->right_spacing;
width += exposable->particle->right_margin;
return width;
}
static void
expose(const struct exposable *exposable, cairo_t *cr, int x, int y, int height)
{
const struct exposable_private *e = exposable->private;
int left_margin = exposable->particle->left_margin;
int left_spacing = e->left_spacing;
int right_spacing = e->right_spacing;
x += left_margin - left_spacing;
for (size_t i = 0; i < e->count; i++) {
const struct exposable *ee = e->exposables[i];
ee->expose(ee, cr, x + left_spacing, y, height);
x += left_spacing + e->widths[i] + right_spacing;
}
}
static void
exposable_destroy(struct exposable *exposable)
{
struct exposable_private *e = exposable->private;
for (size_t i = 0; i < e->count; i++)
e->exposables[i]->destroy(e->exposables[i]);
free(e->exposables);
free(e->widths);
free(e);
free(exposable);
}
static struct exposable *
instantiate(const struct particle *particle, const struct tag_set *tags)
{
const struct particle_private *p = particle->private;
struct exposable_private *e = malloc(sizeof(*e));
e->exposables = malloc(p->count * sizeof(*e->exposables));
e->widths = malloc(p->count * sizeof(*e->widths));
e->count = p->count;
e->left_spacing = p->left_spacing;
e->right_spacing = p->right_spacing;
for (size_t i = 0; i < p->count; i++) {
const struct particle *pp = p->particles[i];
e->exposables[i] = pp->instantiate(pp, tags);
}
struct exposable *exposable = malloc(sizeof(*exposable));
exposable->private = e;
exposable->particle = particle;
exposable->destroy = &exposable_destroy;
exposable->begin_expose = &begin_expose;
exposable->expose = &expose;
return exposable;
}
static void
particle_destroy(struct particle *particle)
{
struct particle_private *p = particle->private;
for (size_t i = 0; i < p->count; i++)
p->particles[i]->destroy(p->particles[i]);
free(p->particles);
free(p);
free(particle);
}
struct particle *
particle_list_new(
struct particle *particles[], size_t count,
int left_spacing, int right_spacing, int left_margin, int right_margin)
{
struct particle_private *p = malloc(sizeof(*p));
p->particles = malloc(count * sizeof(p->particles[0]));
p->count = count;
p->left_spacing = left_spacing;
p->right_spacing = right_spacing;
for (size_t i = 0; i < count; i++)
p->particles[i] = particles[i];
struct particle *particle = particle_common_new(left_margin, right_margin);
particle->private = p;
particle->destroy = &particle_destroy;
particle->instantiate = &instantiate;
/* Claim ownership */
for (size_t i = 0; i < count; i++)
p->particles[i]->parent = particle;
return particle;
}

6
particles/list.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "../particle.h"
struct particle *particle_list_new(
struct particle *particles[], size_t count,
int left_spacing, int right_spacing, int left_margin, int right_margin);

195
particles/string.c Normal file
View file

@ -0,0 +1,195 @@
#include "string.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
struct private {
char *text;
struct font *font;
struct rgba foreground;
};
static int
begin_expose(const struct exposable *exposable, cairo_t *cr)
{
const struct private *e = exposable->private;
cairo_scaled_font_t *scaled = font_use_in_cairo(e->font, cr);
cairo_text_extents_t extents;
cairo_scaled_font_text_extents(scaled, e->text, &extents);
return (exposable->particle->left_margin +
extents.x_advance +
exposable->particle->right_margin);
}
static void
expose(const struct exposable *exposable, cairo_t *cr, int x, int y, int height)
{
const struct private *e = exposable->private;
cairo_scaled_font_t *scaled = font_use_in_cairo(e->font, cr);
cairo_text_extents_t extents;
cairo_scaled_font_text_extents(scaled, e->text, &extents);
cairo_glyph_t *glyphs = NULL;
cairo_text_cluster_t *clusters = NULL;
cairo_text_cluster_flags_t cluster_flags;
int num_glyphs, num_clusters;
cairo_scaled_font_text_to_glyphs(
scaled,
x + exposable->particle->left_margin,
(double)y + ((double)height - extents.y_bearing) / 2,
e->text, strlen(e->text), &glyphs, &num_glyphs,
&clusters, &num_clusters, &cluster_flags);
cairo_set_source_rgba(cr,
e->foreground.red,
e->foreground.green,
e->foreground.blue,
e->foreground.alpha);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_show_text_glyphs(cr, e->text, strlen(e->text),
glyphs, num_glyphs,
clusters, num_clusters, cluster_flags);
cairo_glyph_free(glyphs);
cairo_text_cluster_free(clusters);
/*cairo_scaled_font_destroy(scaled);*/
}
static void
exposable_destroy(struct exposable *exposable)
{
struct private *e = exposable->private;
free(e->text);
free(e);
free(exposable);
}
struct sbuf {
char *s;
size_t size;
size_t len;
};
static void
sbuf_strncat(struct sbuf *s1, const char *s2, size_t n)
{
size_t s2_actual_len = strlen(s2);
size_t s2_len = s2_actual_len < n ? s2_actual_len : n;
if (s1->len + s2_len >= s1->size) {
size_t required_size = s1->len + s2_len + 1;
s1->size = 2 * required_size;
s1->s = realloc(s1->s, s1->size);
s1->s[s1->len] = '\0';
}
strncat(s1->s, s2, s2_len);
s1->len += s2_len;
}
static void
sbuf_strcat(struct sbuf *s1, const char *s2)
{
sbuf_strncat(s1, s2, strlen(s2));
}
static struct exposable *
instantiate(const struct particle *particle, const struct tag_set *tags)
{
const struct private *p = particle->private;
struct private *e = malloc(sizeof(*e));
struct sbuf formatted = {0};
const char *src = p->text;
while (true) {
/* Find next tag opening '{' */
const char *begin = strchr(src, '{');
if (begin == NULL) {
/* No more tags, copy remaining characters */
sbuf_strcat(&formatted, src);
break;
}
/* Find closing '}' */
const char *end = strchr(begin, '}');
if (end == NULL) {
/* Wasn't actually a tag, copy as-is instead */
sbuf_strncat(&formatted, src, begin - src + 1);
src = begin + 1;
continue;
}
/* Extract tag name */
char tag_name[end - begin];
strncpy(tag_name, begin + 1, end - begin - 1);
tag_name[end - begin - 1] = '\0';
/* Lookup tag */
const struct tag *tag = tag_for_name(tags, tag_name);
if (tag == NULL) {
/* No such tag, copy as-is instead */
sbuf_strncat(&formatted, src, begin - src + 1);
src = begin + 1;
continue;
}
/* Copy characters preceeding the tag (name) */
sbuf_strncat(&formatted, src, begin - src);
/* Copy tag value */
const char *value = tag->value(tag);
sbuf_strcat(&formatted, value);
/* Skip past tag name + closing '}' */
src = end + 1;
}
e->text = formatted.s;
e->font = p->font;
e->foreground = p->foreground;
struct exposable *exposable = malloc(sizeof(*exposable));
exposable->particle = particle;
exposable->private = e;
exposable->destroy = &exposable_destroy;
exposable->begin_expose = &begin_expose;
exposable->expose = &expose;
return exposable;
}
static void
particle_destroy(struct particle *particle)
{
struct private *p = particle->private;
font_destroy(p->font);
free(p->text);
free(p);
free(particle);
}
struct particle *
particle_string_new(const char *text, struct font *font,
struct rgba foreground, int left_margin, int right_margin)
{
struct private *p = malloc(sizeof(*p));
p->text = strdup(text);
p->font = font;
p->foreground = foreground;
struct particle *particle = particle_common_new(left_margin, right_margin);
particle->private = p;
particle->destroy = &particle_destroy;
particle->instantiate = &instantiate;
return particle;
}

6
particles/string.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "../particle.h"
struct particle *particle_string_new(
const char *text, struct font *font, struct rgba foreground,
int left_margin, int right_margin);

134
tag.c Normal file
View file

@ -0,0 +1,134 @@
#include "tag.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
struct private {
char *name;
union {
long value_as_int;
double value_as_float;
char *value_as_string;
};
};
static const char *
tag_name(const struct tag *tag)
{
const struct private *priv = tag->private;
return priv->name;
}
static void
destroy_int_and_float(struct tag *tag)
{
struct private *priv = tag->private;
free(priv->name);
free(priv);
free(tag);
}
static void
destroy_string(struct tag *tag)
{
struct private *priv = tag->private;
free(priv->value_as_string);
destroy_int_and_float(tag);
}
static const char *
value_int(const struct tag *tag)
{
static char as_string[128];
const struct private *priv = tag->private;
snprintf(as_string, sizeof(as_string), "%ld", priv->value_as_int);
return as_string;
}
static const char *
value_float(const struct tag *tag)
{
static char as_string[128];
const struct private *priv = tag->private;
snprintf(as_string, sizeof(as_string), "%.2f", priv->value_as_float);
return as_string;
}
static const char *
value_string(const struct tag *tag)
{
const struct private *priv = tag->private;
return priv->value_as_string;
}
struct tag *
tag_new_int(const char *name, long value)
{
struct private *priv = malloc(sizeof(*priv));
priv->name = strdup(name);
priv->value_as_int = value;
struct tag *tag = malloc(sizeof(*tag));
tag->private = priv;
tag->destroy = &destroy_int_and_float;
tag->name = &tag_name;
tag->value = &value_int;
return tag;
}
struct tag *
tag_new_float(const char *name, double value)
{
struct private *priv = malloc(sizeof(*priv));
priv->name = strdup(name);
priv->value_as_float = value;
struct tag *tag = malloc(sizeof(*tag));
tag->private = priv;
tag->destroy = &destroy_int_and_float;
tag->name = &tag_name;
tag->value = &value_float;
return tag;
}
struct tag *
tag_new_string(const char *name, const char *value)
{
struct private *priv = malloc(sizeof(*priv));
priv->name = strdup(name);
priv->value_as_string = strdup(value);
struct tag *tag = malloc(sizeof(*tag));
tag->private = priv;
tag->destroy = &destroy_string;
tag->name = &tag_name;
tag->value = &value_string;
return tag;
}
const struct tag *
tag_for_name(const struct tag_set *set, const char *name)
{
if (set == NULL)
return NULL;
for (size_t i = 0; i < set->count; i++) {
const struct tag *tag = set->tags[i];
if (strcmp(tag->name(tag), name) == 0)
return tag;
}
return NULL;
}
void
tag_set_destroy(struct tag_set *set)
{
for (size_t i = 0; i < set->count; i++)
set->tags[i]->destroy(set->tags[i]);
set->tags = NULL;
set->count = 0;
}

23
tag.h Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <stddef.h>
struct tag {
void *private;
void (*destroy)(struct tag *tag);
const char *(*name)(const struct tag *tag);
const char *(*value)(const struct tag *tag);
};
struct tag_set {
struct tag **tags;
size_t count;
};
struct tag *tag_new_int(const char *name, long value);
struct tag *tag_new_float(const char *name, double value);
struct tag *tag_new_string(const char *name, const char *value);
const struct tag *tag_for_name(const struct tag_set *set, const char *name);
void tag_set_destroy(struct tag_set *set);

37
xcb.c Normal file
View file

@ -0,0 +1,37 @@
#include "xcb.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
xcb_atom_t
get_atom(xcb_connection_t *conn, const char *name)
{
xcb_generic_error_t *e;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
conn,
xcb_intern_atom(conn, 1, strlen(name), name),
&e);
assert(e == NULL);
xcb_atom_t ret = reply->atom;
free(reply);
return ret;
}
char *
get_atom_name(xcb_connection_t *conn, xcb_atom_t atom)
{
xcb_generic_error_t *e;
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
conn, xcb_get_atom_name(conn, atom), &e);
assert(e == NULL);
int len = xcb_get_atom_name_name_length(reply);
char *name = malloc(len + 1);
memcpy(name, xcb_get_atom_name_name(reply), len);
name[len] = '\0';
free(reply);
return name;
}

6
xcb.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include <xcb/xcb.h>
xcb_atom_t get_atom(xcb_connection_t *conn, const char *name);
char * get_atom_name(xcb_connection_t *conn, xcb_atom_t atom);

486
yml.c Normal file
View file

@ -0,0 +1,486 @@
#include "yml.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <yaml.h>
enum node_type {
ROOT,
SCALAR,
DICT,
LIST,
};
struct yml_node;
struct llist_element {
struct yml_node *node;
struct llist_element *next;
};
struct llist {
struct llist_element *head;
struct llist_element *tail;
};
struct yml_node {
enum node_type type;
union {
struct yml_node *root;
struct {
char *value;
} scalar;
struct {
struct llist keys;
struct llist values;
size_t key_count;
size_t value_count;
} dict;
struct {
struct llist values;
} list;
};
struct yml_node *parent;
};
static void
llist_add(struct llist *list, struct yml_node *node)
{
struct llist_element *element = malloc(sizeof(*element));
element->node = node;
element->next = NULL;
if (list->tail == NULL) {
assert(list->head == NULL);
list->head = list->tail = element;
} else {
assert(list->head != NULL);
list->tail->next = element;
}
list->tail = element;
}
static void
add_node(struct yml_node *parent, struct yml_node *new_node)
{
switch (parent->type) {
case ROOT:
assert(parent->root == NULL);
parent->root = new_node;
new_node->parent = parent;
break;
case DICT:
if (parent->dict.key_count == parent->dict.value_count) {
llist_add(&parent->dict.keys, new_node);
parent->dict.key_count++;
} else {
llist_add(&parent->dict.values, new_node);
parent->dict.value_count++;
}
new_node->parent = parent;
break;
case LIST:
llist_add(&parent->list.values, new_node);
new_node->parent = parent;
break;
case SCALAR:
assert(false);
break;
}
}
struct yml_node *
yml_load(FILE *yml)
{
yaml_parser_t yaml;
yaml_parser_initialize(&yaml);
//FILE *yml = fopen("yml.yml", "r");
//assert(yml != NULL);
yaml_parser_set_input_file(&yaml, yml);
bool done = false;
int indent = 0;
struct yml_node *root = malloc(sizeof(*root));
root->type = ROOT;
root->root = NULL;
struct yml_node *n = root;
while (!done) {
yaml_event_t event;
if (!yaml_parser_parse(&yaml, &event)) {
//printf("yaml parser error\n");
/* TODO: free node tree */
root = NULL;
break;
}
switch (event.type) {
case YAML_NO_EVENT:
//printf("%*sNO EVENT\n", indent, "");
break;
case YAML_STREAM_START_EVENT:
//printf("%*sSTREAM START\n", indent, "");
indent += 2;
break;
case YAML_STREAM_END_EVENT:
indent -= 2;
//printf("%*sSTREAM END\n", indent, "");
done = true;
break;
case YAML_DOCUMENT_START_EVENT:
//printf("%*sDOC START\n", indent, "");
indent += 2;
break;
case YAML_DOCUMENT_END_EVENT:
indent -= 2;
//printf("%*sDOC END\n", indent, "");
break;
case YAML_ALIAS_EVENT:
//printf("%*sALIAS\n", indent, "");
assert(false);
break;
case YAML_SCALAR_EVENT: {
/*
* printf("%*sSCALAR: %.*s\n", indent, "",
* (int)event.data.scalar.length, event.data.scalar.value);
*/
struct yml_node *s = calloc(1, sizeof(*s));
s->type = SCALAR;
s->scalar.value = strndup(
(const char*)event.data.scalar.value, event.data.scalar.length);
add_node(n, s);
break;
}
case YAML_SEQUENCE_START_EVENT: {
//printf("%*sSEQ START\n", indent, "");
indent += 2;
struct yml_node *l = calloc(1, sizeof(*l));
l->type = LIST;
add_node(n, l);
n = l;
break;
}
case YAML_SEQUENCE_END_EVENT:
indent -= 2;
//printf("%*sSEQ END\n", indent, "");
assert(n->parent != NULL);
n = n->parent;
break;
case YAML_MAPPING_START_EVENT: {
//printf("%*sMAP START\n", indent, "");
indent += 2;
struct yml_node *m = calloc(1, sizeof(*m));
m->type = DICT;
add_node(n, m);
n = m;
break;
}
case YAML_MAPPING_END_EVENT:
indent -= 2;
//printf("%*sMAP END\n", indent, "");
assert(n->parent != NULL);
n = n->parent;
break;
}
yaml_event_delete(&event);
}
yaml_parser_delete(&yaml);
//print_node(root, 0);
return root;
}
void
yml_destroy(struct yml_node *node)
{
switch (node->type) {
case ROOT:
yml_destroy(node->root);
break;
case SCALAR:
free(node->scalar.value);
break;
case LIST:
for (struct llist_element *e = node->list.values.head,
*n = e ? e->next : NULL;
e != NULL;
e = n, n = n ? n->next : NULL)
{
yml_destroy(e->node);
free(e);
}
break;
case DICT:
for (struct llist_element *key = node->dict.keys.head,
*value = node->dict.values.head,
*n_key = key ? key->next : NULL,
*n_value = value ? value->next : NULL;
key != NULL;
key = n_key, value = n_value,
n_key = n_key ? n_key->next : NULL,
n_value = n_value ? n_value->next : NULL)
{
yml_destroy(value->node);
yml_destroy(key->node);
free(key);
free(value);
}
break;
}
free(node);
}
bool
yml_is_scalar(const struct yml_node *node)
{
return node->type == SCALAR;
}
bool
yml_is_dict(const struct yml_node *node)
{
return node->type == DICT;
}
bool
yml_is_list(const struct yml_node *node)
{
return node->type == LIST;
}
const struct yml_node *
yml_get_value(const struct yml_node *node, const char *_path)
{
char *path = strdup(_path);
if (node->type == ROOT)
node = node->root;
for (const char *part = strtok(path, "."), *next_part = strtok(NULL, ".");
part != NULL;
part = next_part, next_part = strtok(NULL, "."))
{
assert(yml_is_dict(node));
for (const struct llist_element *key = node->dict.keys.head,
*value = node->dict.values.head;
key != NULL;
key = key->next, value = value->next)
{
assert(yml_is_scalar(key->node));
if (strcmp(key->node->scalar.value, part) == 0) {
if (next_part == NULL) {
free(path);
return value->node;
}
node = value->node;
break;
}
}
}
free(path);
return NULL;
}
struct yml_list_iter
yml_list_iter(const struct yml_node *list)
{
assert(yml_is_list(list));
const struct llist_element *element = list->list.values.head;
return (struct yml_list_iter){
.node = element != NULL ? element->node : NULL,
.private = element};
}
void
yml_list_next(struct yml_list_iter *iter)
{
const struct llist_element *element = iter->private;
if (element == NULL)
return;
const struct llist_element *next = element->next;
iter->node = next != NULL ? next->node : NULL;
iter->private = next;
}
size_t
yml_list_length(const struct yml_node *list)
{
assert(yml_is_list(list));
size_t length = 0;
for (struct yml_list_iter it = yml_list_iter(list);
it.node != NULL;
yml_list_next(&it), length++)
;
return length;
}
struct yml_dict_iter
yml_dict_iter(const struct yml_node *dict)
{
assert(yml_is_dict(dict));
const struct llist_element *key = dict->dict.keys.head;
const struct llist_element *value = dict->dict.values.head;
assert((key == NULL && value == NULL) ||
(key != NULL && value != NULL));
return (struct yml_dict_iter){
.key = key != NULL ? key->node : NULL,
.value = value != NULL ? value->node : NULL,
.private1 = key,
.private2 = value,
};
}
void
yml_dict_next(struct yml_dict_iter *iter)
{
const struct llist_element *key = iter->private1;
const struct llist_element *value = iter->private2;
if (key == NULL)
return;
const struct llist_element *next_key = key->next;
const struct llist_element *next_value = value->next;
iter->key = next_key != NULL ? next_key->node : NULL;
iter->value = next_value != NULL ? next_value->node : NULL;
iter->private1 = next_key;
iter->private2 = next_value;
}
size_t
yml_dict_length(const struct yml_node *dict)
{
assert(yml_is_dict(dict));
assert(dict->dict.key_count == dict->dict.value_count);
return dict->dict.key_count;
}
const char *
yml_value_as_string(const struct yml_node *value)
{
assert(yml_is_scalar(value));
return value->scalar.value;
}
long
yml_value_as_int(const struct yml_node *value)
{
assert(yml_is_scalar(value));
long ival;
int res = sscanf(yml_value_as_string(value), "%ld", &ival);
return res != 1 ? -1 : ival;
}
bool
yml_value_as_bool(const struct yml_node *value)
{
const char *v = yml_value_as_string(value);
if (strcasecmp(v, "y") == 0 ||
strcasecmp(v, "yes") == 0 ||
strcasecmp(v, "true") == 0 ||
strcasecmp(v, "on") == 0)
{
return true;
} else if (strcasecmp(v, "n") == 0 ||
strcasecmp(v, "no") == 0 ||
strcasecmp(v, "false") == 0 ||
strcasecmp(v, "off") == 0)
{
return false;
} else
assert(false);
}
static void
_print_node(const struct yml_node *n, int indent)
{
switch (n->type) {
case ROOT:
_print_node(n->root, indent);
break;
case DICT:
assert(n->dict.key_count == n->dict.value_count);
for (const struct llist_element *k = n->dict.keys.head, *v = n->dict.values.head;
k != NULL; k = k->next, v = v->next)
{
_print_node(k->node, indent);
printf(": ");
if (v->node->type != SCALAR) {
printf("\n");
_print_node(v->node, indent + 2);
} else {
_print_node(v->node, 0);
printf("\n");
}
}
break;
case LIST:
for (const struct llist_element *v = n->list.values.head;
v != NULL;
v = v->next)
{
printf("%*s- ", indent, "");
if (v->node->type != SCALAR) {
printf("\n");
_print_node(v->node, indent + 2);
} else {
_print_node(v->node, 0);
}
}
break;
case SCALAR:
printf("%*s%s", indent, "", n->scalar.value);
break;
}
}
void
print_node(const struct yml_node *n)
{
_print_node(n, 0);
}

40
yml.h Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
struct yml_node;
struct yml_node *yml_load(FILE *yml);
void yml_destroy(struct yml_node *root);
bool yml_is_scalar(const struct yml_node *node);
bool yml_is_dict(const struct yml_node *node);
bool yml_is_list(const struct yml_node *node);
const struct yml_node *yml_get_value(
const struct yml_node *node, const char *path);
struct yml_list_iter {
const struct yml_node *node;
const void *private;
};
struct yml_list_iter yml_list_iter(const struct yml_node *list);
void yml_list_next(struct yml_list_iter *iter);
size_t yml_list_length(const struct yml_node *list);
struct yml_dict_iter {
const struct yml_node *key;
const struct yml_node *value;
const void *private1;
const void *private2;
};
struct yml_dict_iter yml_dict_iter(const struct yml_node *dict);
void yml_dict_next(struct yml_dict_iter *iter);
size_t yml_dict_length(const struct yml_node *dict);
const char *yml_value_as_string(const struct yml_node *value);
long yml_value_as_int(const struct yml_node *value);
bool yml_value_as_bool(const struct yml_node *value);
/* For debugging, prints on stdout */
void print_node(const struct yml_node *n);