From 8bf8a398b9d44176c1ec14a7ffe67b6782a3e30f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 17 Nov 2018 11:30:33 +0100 Subject: [PATCH] initial commit: wip --- .gitignore | 1 + CMakeLists.txt | 55 +++++ bar.c | 579 +++++++++++++++++++++++++++++++++++++++++++++ bar.h | 44 ++++ color.h | 8 + config.c | 308 ++++++++++++++++++++++++ config.h | 7 + font.c | 92 +++++++ font.h | 22 ++ main.c | 132 +++++++++++ module.c | 27 +++ module.h | 46 ++++ modules/clock.c | 90 +++++++ modules/clock.h | 6 + modules/label.c | 51 ++++ modules/label.h | 6 + modules/xwindow.c | 336 ++++++++++++++++++++++++++ modules/xwindow.h | 6 + particle.c | 12 + particle.h | 32 +++ particles/list.c | 128 ++++++++++ particles/list.h | 6 + particles/string.c | 195 +++++++++++++++ particles/string.h | 6 + tag.c | 134 +++++++++++ tag.h | 23 ++ xcb.c | 37 +++ xcb.h | 6 + yml.c | 486 +++++++++++++++++++++++++++++++++++++ yml.h | 40 ++++ 30 files changed, 2921 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 bar.c create mode 100644 bar.h create mode 100644 color.h create mode 100644 config.c create mode 100644 config.h create mode 100644 font.c create mode 100644 font.h create mode 100644 main.c create mode 100644 module.c create mode 100644 module.h create mode 100644 modules/clock.c create mode 100644 modules/clock.h create mode 100644 modules/label.c create mode 100644 modules/label.h create mode 100644 modules/xwindow.c create mode 100644 modules/xwindow.h create mode 100644 particle.c create mode 100644 particle.h create mode 100644 particles/list.c create mode 100644 particles/list.h create mode 100644 particles/string.c create mode 100644 particles/string.h create mode 100644 tag.c create mode 100644 tag.h create mode 100644 xcb.c create mode 100644 xcb.h create mode 100644 yml.c create mode 100644 yml.h 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);