commit 8bf8a398b9d44176c1ec14a7ffe67b6782a3e30f Author: Daniel Eklöf Date: Sat Nov 17 11:30:33 2018 +0100 initial commit: wip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68f8eef --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bld/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..17e28ce --- /dev/null +++ b/CMakeLists.txt @@ -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} + ) diff --git a/bar.c b/bar.c new file mode 100644 index 0000000..2a5f5c6 --- /dev/null +++ b/bar.c @@ -0,0 +1,579 @@ +#include "bar.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#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; +} diff --git a/bar.h b/bar.h new file mode 100644 index 0000000..7ae9d88 --- /dev/null +++ b/bar.h @@ -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); diff --git a/color.h b/color.h new file mode 100644 index 0000000..113a13f --- /dev/null +++ b/color.h @@ -0,0 +1,8 @@ +#pragma once + +struct rgba { + double red; + double green; + double blue; + double alpha; +}; diff --git a/config.c b/config.c new file mode 100644 index 0000000..3327b8a --- /dev/null +++ b/config.c @@ -0,0 +1,308 @@ +#include "config.h" + +#include +#include +#include +#include + +#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; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..422f782 --- /dev/null +++ b/config.h @@ -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); diff --git a/font.c b/font.c new file mode 100644 index 0000000..50f8aca --- /dev/null +++ b/font.c @@ -0,0 +1,92 @@ +#include "font.h" +#include +#include +#include + +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); +} diff --git a/font.h b/font.h new file mode 100644 index 0000000..b8f10c6 --- /dev/null +++ b/font.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#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); diff --git a/main.c b/main.c new file mode 100644 index 0000000..93b5319 --- /dev/null +++ b/main.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#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; +} diff --git a/module.c b/module.c new file mode 100644 index 0000000..1ff828a --- /dev/null +++ b/module.c @@ -0,0 +1,27 @@ +#include "module.h" +#include + +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); +} diff --git a/module.h b/module.h new file mode 100644 index 0000000..2805649 --- /dev/null +++ b/module.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#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); diff --git a/modules/clock.c b/modules/clock.c new file mode 100644 index 0000000..bf3cfce --- /dev/null +++ b/modules/clock.c @@ -0,0 +1,90 @@ +#include "clock.h" +#include +#include +#include + +#include + +#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; +} diff --git a/modules/clock.h b/modules/clock.h new file mode 100644 index 0000000..694ede4 --- /dev/null +++ b/modules/clock.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../module.h" +#include "../particle.h" + +struct module *module_clock(struct particle *label); diff --git a/modules/label.c b/modules/label.c new file mode 100644 index 0000000..df71c62 --- /dev/null +++ b/modules/label.c @@ -0,0 +1,51 @@ +#include "label.h" + +#include +#include + +#include + +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; +} diff --git a/modules/label.h b/modules/label.h new file mode 100644 index 0000000..7f04a10 --- /dev/null +++ b/modules/label.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../module.h" +#include "../particle.h" + +struct module *module_label(struct particle *label); diff --git a/modules/xwindow.c b/modules/xwindow.c new file mode 100644 index 0000000..89bc174 --- /dev/null +++ b/modules/xwindow.c @@ -0,0 +1,336 @@ +#include "xwindow.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#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; +} diff --git a/modules/xwindow.h b/modules/xwindow.h new file mode 100644 index 0000000..e178ff8 --- /dev/null +++ b/modules/xwindow.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../module.h" +#include "../particle.h" + +struct module *module_xwindow(struct particle *label); diff --git a/particle.c b/particle.c new file mode 100644 index 0000000..c763776 --- /dev/null +++ b/particle.c @@ -0,0 +1,12 @@ +#include "particle.h" +#include + +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; +} diff --git a/particle.h b/particle.h new file mode 100644 index 0000000..c347d1f --- /dev/null +++ b/particle.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#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); diff --git a/particles/list.c b/particles/list.c new file mode 100644 index 0000000..45fb846 --- /dev/null +++ b/particles/list.c @@ -0,0 +1,128 @@ +#include "list.h" +#include + +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; +} diff --git a/particles/list.h b/particles/list.h new file mode 100644 index 0000000..5139ba3 --- /dev/null +++ b/particles/list.h @@ -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); diff --git a/particles/string.c b/particles/string.c new file mode 100644 index 0000000..e920986 --- /dev/null +++ b/particles/string.c @@ -0,0 +1,195 @@ +#include "string.h" + +#include +#include +#include + +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; +} diff --git a/particles/string.h b/particles/string.h new file mode 100644 index 0000000..c0b88b9 --- /dev/null +++ b/particles/string.h @@ -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); diff --git a/tag.c b/tag.c new file mode 100644 index 0000000..c152630 --- /dev/null +++ b/tag.c @@ -0,0 +1,134 @@ +#include "tag.h" +#include +#include +#include + +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; +} diff --git a/tag.h b/tag.h new file mode 100644 index 0000000..78f0936 --- /dev/null +++ b/tag.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +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); diff --git a/xcb.c b/xcb.c new file mode 100644 index 0000000..5c7ca75 --- /dev/null +++ b/xcb.c @@ -0,0 +1,37 @@ +#include "xcb.h" + +#include +#include +#include + +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; +} diff --git a/xcb.h b/xcb.h new file mode 100644 index 0000000..76c6fd7 --- /dev/null +++ b/xcb.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +xcb_atom_t get_atom(xcb_connection_t *conn, const char *name); +char * get_atom_name(xcb_connection_t *conn, xcb_atom_t atom); diff --git a/yml.c b/yml.c new file mode 100644 index 0000000..af7bede --- /dev/null +++ b/yml.c @@ -0,0 +1,486 @@ +#include "yml.h" + +#include +#include +#include +#include + +#include + +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); +} diff --git a/yml.h b/yml.h new file mode 100644 index 0000000..b079b58 --- /dev/null +++ b/yml.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +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);