From 910c540ea912c70cba733d24264abf01cbae6019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Jun 2019 20:08:54 +0200 Subject: [PATCH] initial commit: maps an XDG toplevel window --- .gitignore | 1 + log.c | 146 +++++++++++++++++++++++++++ log.h | 35 +++++++ main.c | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++ meson.build | 62 ++++++++++++ shm.c | 163 ++++++++++++++++++++++++++++++ shm.h | 24 +++++ tllist.h | 157 ++++++++++++++++++++++++++++ 8 files changed, 874 insertions(+) create mode 100644 .gitignore create mode 100644 log.c create mode 100644 log.h create mode 100644 main.c create mode 100644 meson.build create mode 100644 shm.c create mode 100644 shm.h create mode 100644 tllist.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..84db87b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/bld diff --git a/log.c b/log.c new file mode 100644 index 00000000..27dd6b42 --- /dev/null +++ b/log.c @@ -0,0 +1,146 @@ +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static bool colorize = false; + +static void __attribute__((constructor)) +init(void) +{ + colorize = isatty(STDOUT_FILENO); + openlog(NULL, /*LOG_PID*/0, LOG_USER); + setlogmask(LOG_UPTO(LOG_WARNING)); +} + +static void __attribute__((destructor)) +fini(void) +{ + closelog(); +} + +static void +_log(enum log_class log_class, const char *module, const char *file, int lineno, + const char *fmt, int sys_errno, va_list va) +{ + const char *class = "abcd"; + int class_clr = 0; + switch (log_class) { + case LOG_CLASS_ERROR: class = " err"; class_clr = 31; break; + case LOG_CLASS_WARNING: class = "warn"; class_clr = 33; break; + case LOG_CLASS_INFO: class = "info"; class_clr = 97; break; + case LOG_CLASS_DEBUG: class = " dbg"; class_clr = 36; break; + } + + char clr[16]; + snprintf(clr, sizeof(clr), "\e[%dm", class_clr); + printf("%s%s%s: ", colorize ? clr : "", class, colorize ? "\e[0m" : ""); + + if (colorize) + printf("\e[2m"); +#if defined(_DEBUG) + printf("%s:%d: ", file, lineno); +#else + printf("%s: ", module); +#endif + if (colorize) + printf("\e[0m"); + + vprintf(fmt, va); + + if (sys_errno != 0) + printf(": %s", strerror(sys_errno)); + + printf("\n"); +} + +static void +_sys_log(enum log_class log_class, const char *module, const char *file, + int lineno, const char *fmt, int sys_errno, va_list va) +{ + /* Map our log level to syslog's level */ + int level = -1; + switch (log_class) { + case LOG_CLASS_ERROR: level = LOG_ERR; break; + case LOG_CLASS_WARNING: level = LOG_WARNING; break; + case LOG_CLASS_INFO: level = LOG_INFO; break; + case LOG_CLASS_DEBUG: level = LOG_DEBUG; break; + } + + assert(level != -1); + + const char *sys_err = sys_errno != 0 ? strerror(sys_errno) : NULL; + + va_list va2; + va_copy(va2, va); + + /* Calculate required size of buffer holding the entire log message */ + int required_len = 0; + required_len += strlen(module) + 2; /* "%s: " */ + required_len += vsnprintf(NULL, 0, fmt, va2); va_end(va2); + + if (sys_errno != 0) + required_len += strlen(sys_err) + 2; /* ": %s" */ + + /* Format the msg */ + char *msg = malloc(required_len + 1); + int idx = 0; + + idx += snprintf(&msg[idx], required_len + 1 - idx, "%s: ", module); + idx += vsnprintf(&msg[idx], required_len + 1 - idx, fmt, va); + + if (sys_errno != 0) { + idx += snprintf( + &msg[idx], required_len + 1 - idx, ": %s", strerror(sys_errno)); + } + + syslog(level, "%s", msg); + free(msg); +} + +void +log_msg(enum log_class log_class, const char *module, + const char *file, int lineno, const char *fmt, ...) +{ + va_list ap1, ap2; + va_start(ap1, fmt); + va_copy(ap2, ap1); + _log(log_class, module, file, lineno, fmt, 0, ap1); + _sys_log(log_class, module, file, lineno, fmt, 0, ap2); + va_end(ap1); + va_end(ap2); +} + +void log_errno(enum log_class log_class, const char *module, + const char *file, int lineno, + const char *fmt, ...) +{ + va_list ap1, ap2; + va_start(ap1, fmt); + va_copy(ap2, ap1); + _log(log_class, module, file, lineno, fmt, errno, ap1); + _sys_log(log_class, module, file, lineno, fmt, errno, ap2); + va_end(ap1); + va_end(ap2); +} + +void log_errno_provided(enum log_class log_class, const char *module, + const char *file, int lineno, int _errno, + const char *fmt, ...) +{ + va_list ap1, ap2; + va_start(ap1, fmt); + va_copy(ap2, ap1); + _log(log_class, module, file, lineno, fmt, _errno, ap1); + _sys_log(log_class, module, file, lineno, fmt, _errno, ap2); + va_end(ap1); + va_end(ap2); +} diff --git a/log.h b/log.h new file mode 100644 index 00000000..ba32d037 --- /dev/null +++ b/log.h @@ -0,0 +1,35 @@ +#pragma once + +enum log_class { LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG }; + +void log_msg(enum log_class log_class, const char *module, + const char *file, int lineno, + const char *fmt, ...) __attribute__((format (printf, 5, 6))); + +void log_errno(enum log_class log_class, const char *module, + const char *file, int lineno, + const char *fmt, ...) __attribute__((format (printf, 5, 6))); + +void log_errno_provided( + enum log_class log_class, const char *module, + const char *file, int lineno, int _errno, + const char *fmt, ...) __attribute__((format (printf, 6, 7))); + +#define LOG_ERR(fmt, ...) \ + log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__) +#define LOG_ERRNO(fmt, ...) \ + log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__) +#define LOG_ERRNO_P(fmt, _errno, ...) \ + log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, \ + _errno, fmt, ## __VA_ARGS__) +#define LOG_WARN(fmt, ...) \ + log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__) +#define LOG_INFO(fmt, ...) \ + log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__) + +#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG + #define LOG_DBG(fmt, ...) \ + log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, fmt, ## __VA_ARGS__) +#else + #define LOG_DBG(fmt, ...) +#endif diff --git a/main.c b/main.c new file mode 100644 index 00000000..10a1ff78 --- /dev/null +++ b/main.c @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define LOG_MODULE "main" +#define LOG_ENABLE_DBG 1 +#include "log.h" +#include "shm.h" + +struct wayland { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_surface *surface; + struct wl_shm *shm; + struct xdg_wm_base *shell; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; +}; + +struct context { + bool quit; + struct wayland wl; +}; + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ +} + +static const struct wl_shm_listener shm_listener = { + .format = &shm_format, +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + LOG_DBG("wm base ping"); + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = &xdg_wm_base_ping, +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + LOG_DBG("global: %s", interface); + struct context *c = data; + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + c->wl.compositor = wl_registry_bind( + c->wl.registry, name, &wl_compositor_interface, 4); + } + + else if (strcmp(interface, wl_shm_interface.name) == 0) { + c->wl.shm = wl_registry_bind(c->wl.registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(c->wl.shm, &shm_listener, &c->wl); + wl_display_roundtrip(c->wl.display); + } + + else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + c->wl.shell = wl_registry_bind( + c->wl.registry, name, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(c->wl.shell, &xdg_wm_base_listener, c); + } + +#if 0 + else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = wl_registry_bind( + c->wl.registry, name, &wl_output_interface, 3); + + tll_push_back(c->wl.monitors, ((struct monitor){.output = output})); + + struct monitor *mon = &tll_back(c->wl.monitors); + wl_output_add_listener(output, &output_listener, mon); + + /* + * The "output" interface doesn't give us the monitors' + * identifiers (e.g. "LVDS-1"). Use the XDG output interface + * for that. + */ + + assert(c->wl.xdg_output_manager != NULL); + mon->xdg = zxdg_output_manager_v1_get_xdg_output( + c->wl.xdg_output_manager, mon->output); + + zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon); + wl_display_roundtrip(c->wl.display); + } +#endif + +#if 0 + else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + c->wl.layer_shell = wl_registry_bind( + c->wl.registry, name, &zwlr_layer_shell_v1_interface, 1); + } +#endif + +#if 0 + else if (strcmp(interface, wl_seat_interface.name) == 0) { + c->wl.seat = wl_registry_bind(c->wl.registry, name, &wl_seat_interface, 4); + wl_seat_add_listener(c->wl.seat, &seat_listener, c); + wl_display_roundtrip(c->wl.display); + } +#endif + +#if 0 + else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + c->wl.xdg_output_manager = wl_registry_bind( + c->wl.registry, name, &zxdg_output_manager_v1_interface, 2); + } +#endif +} + +static void +xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + //struct context *c = data; + LOG_DBG("xdg-toplevel: configure: %dx%d", width, height); +} + +static void +xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + struct context *c = data; + LOG_DBG("xdg-toplevel: close"); + c->quit = true; + wl_display_roundtrip(c->wl.display); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = &xdg_toplevel_configure, + .close = &xdg_toplevel_close, +}; + +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, + uint32_t serial) +{ + LOG_DBG("xdg-surface: configure"); + xdg_surface_ack_configure(xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = &xdg_surface_configure, +}; + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + LOG_WARN("global removed: %u", name); + assert(false); +} + +static const struct wl_registry_listener registry_listener = { + .global = &handle_global, + .global_remove = &handle_global_remove, +}; + +int +main(int argc, const char *const *argv) +{ + int ret = EXIT_FAILURE; + + struct context c = { + .quit = false, + .wl = {0}, + }; + + c.wl.display = wl_display_connect(NULL); + if (c.wl.display == NULL) { + LOG_ERR("failed to connect to wayland; no compositor running?"); + goto out; + } + + c.wl.registry = wl_display_get_registry(c.wl.display); + if (c.wl.registry == NULL) { + LOG_ERR("failed to get wayland registry"); + goto out; + } + + wl_registry_add_listener(c.wl.registry, ®istry_listener, &c); + wl_display_roundtrip(c.wl.display); + + if (c.wl.compositor == NULL) { + LOG_ERR("no compositor"); + goto out; + } + if (c.wl.shm == NULL) { + LOG_ERR("no shared memory buffers interface"); + goto out; + } + if (c.wl.shell == NULL) { + LOG_ERR("no XDG shell interface"); + goto out; + } + + c.wl.surface = wl_compositor_create_surface(c.wl.compositor); + if (c.wl.surface == NULL) { + LOG_ERR("failed to create wayland surface"); + goto out; + } + + c.wl.xdg_surface = xdg_wm_base_get_xdg_surface(c.wl.shell, c.wl.surface); + xdg_surface_add_listener(c.wl.xdg_surface, &xdg_surface_listener, &c); + + c.wl.xdg_toplevel = xdg_surface_get_toplevel(c.wl.xdg_surface); + xdg_toplevel_add_listener(c.wl.xdg_toplevel, &xdg_toplevel_listener, &c); + + xdg_toplevel_set_app_id(c.wl.xdg_toplevel, "f00ter"); + xdg_toplevel_set_title(c.wl.xdg_toplevel, "hello world"); + + wl_surface_commit(c.wl.surface); + wl_display_roundtrip(c.wl.display); + + /* TODO: use font metrics to calculate initial size from ROWS x COLS */ + const int default_width = 300; + const int default_height = 300; + struct buffer *buf = shm_get_buffer(c.wl.shm, default_width, default_height); + + cairo_set_operator(buf->cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(buf->cairo, 1.0, 0.0, 0.0, 1.0); + cairo_rectangle(buf->cairo, 0, 0, buf->width, buf->height); + cairo_fill(buf->cairo); + + wl_surface_attach(c.wl.surface, buf->wl_buf, 0, 0); + wl_surface_damage(c.wl.surface, 0, 0, buf->width, buf->height); + + + wl_surface_commit(c.wl.surface); + + wl_display_dispatch_pending(c.wl.display); + + while (!c.quit) { + struct pollfd fds[] = { + {.fd = wl_display_get_fd(c.wl.display), .events = POLLIN}, + }; + + LOG_DBG("polling..."); + wl_display_flush(c.wl.display); + poll(fds, 1, -1); + LOG_DBG("lsdjfldsf"); + + if (fds[0].revents & POLLHUP) { + LOG_WARN("disconnected from wayland"); + break; + } + + wl_display_dispatch(c.wl.display); + } + + ret = EXIT_SUCCESS; + +out: + shm_fini(); + if (c.wl.xdg_toplevel != NULL) + xdg_toplevel_destroy(c.wl.xdg_toplevel); + if (c.wl.xdg_surface != NULL) + xdg_surface_destroy(c.wl.xdg_surface); + if (c.wl.surface != NULL) + wl_surface_destroy(c.wl.surface); + if (c.wl.shell != NULL) + xdg_wm_base_destroy(c.wl.shell); + if (c.wl.shm != NULL) + wl_shm_destroy(c.wl.shm); + if (c.wl.compositor != NULL) + wl_compositor_destroy(c.wl.compositor); + if (c.wl.registry != NULL) + wl_registry_destroy(c.wl.registry); + if (c.wl.display != NULL) + wl_display_disconnect(c.wl.display); + + cairo_debug_reset_static_data(); + return ret; +} diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..b7f608b5 --- /dev/null +++ b/meson.build @@ -0,0 +1,62 @@ +project('f00ter', 'c', + version: '0.9.0', + license: 'MIT', + default_options: [ + 'c_std=c11', + 'warning_level=1', + 'werror=true', + 'b_ndebug=if-release']) + +is_debug_build = get_option('buildtype').startswith('debug') + +add_project_arguments( + ['-D_GNU_SOURCE', + #'-DF00SEL_VERSION=@0@'.format(version)] + + ] + + (is_debug_build ? ['-D_DEBUG'] : []), + language: 'c', +) +fontconfig = dependency('fontconfig') +cairo = dependency('cairo') +cairo_ft = dependency('cairo-ft') +wayland_protocols = dependency('wayland-protocols') +wayland_client = dependency('wayland-client') +wayland_cursor = dependency('wayland-cursor') +xkb = dependency('xkbcommon') + +wayland_protocols_datadir = wayland_protocols.get_pkgconfig_variable('pkgdatadir') + +wscanner = dependency('wayland-scanner', native: true) +wscanner_prog = find_program( + wscanner.get_pkgconfig_variable('wayland_scanner'), native: true) + +wl_proto_headers = [] +wl_proto_src = [] +foreach prot : [ + #'external/wlr-layer-shell-unstable-v1.xml', + wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml', + #wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml'] + ] + + wl_proto_headers += custom_target( + prot.underscorify() + '-client-header', + output: '@BASENAME@.h', + input: prot, + command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@']) + + wl_proto_src += custom_target( + prot.underscorify() + '-private-code', + output: '@BASENAME@.c', + input: prot, + command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) +endforeach + +executable( + 'f00ter', + 'log.c', 'log.h', + 'main.c', + 'shm.c', 'shm.h', + 'tllist.h', + wl_proto_src + wl_proto_headers, + dependencies: [cairo, cairo_ft, fontconfig, wayland_client, wayland_cursor, xkb], + install: true) diff --git a/shm.c b/shm.c new file mode 100644 index 00000000..d691be10 --- /dev/null +++ b/shm.c @@ -0,0 +1,163 @@ +#include "shm.h" + +#include +#include + +#include +#include +#include + +#define LOG_MODULE "shm" +#include "log.h" +#include "tllist.h" + +static tll(struct buffer) buffers; + +static void +buffer_release(void *data, struct wl_buffer *wl_buffer) +{ + struct buffer *buffer = data; + assert(buffer->wl_buf == wl_buffer); + assert(buffer->busy); + buffer->busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = &buffer_release, +}; + +struct buffer * +shm_get_buffer(struct wl_shm *shm, int width, int height) +{ + tll_foreach(buffers, it) { + if (!it->item.busy) { + it->item.busy = true; + return &it->item; + } + } + + /* + * No existing buffer available. Create a new one by: + * + * 1. open a memory backed "file" with memfd_create() + * 2. mmap() the memory file, to be used by the cairo surface + * 3. create a wayland shm buffer for the same memory file + * + * The cairo surface and the wayland buffer are now sharing + * memory. + */ + + int pool_fd = -1; + void *mmapped = NULL; + size_t size = 0; + + struct wl_shm_pool *pool = NULL; + struct wl_buffer *buf = NULL; + + cairo_surface_t *cairo_surface = NULL; + cairo_t *cairo = NULL; + + /* Backing memory for SHM */ + pool_fd = memfd_create("f00sel-wayland-shm-buffer-pool", MFD_CLOEXEC); + if (pool_fd == -1) { + LOG_ERRNO("failed to create SHM backing memory file"); + goto err; + } + + /* Total size */ + const uint32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + size = stride * height; + if (ftruncate(pool_fd, size) == -1) { + LOG_ERRNO("failed to truncate SHM pool"); + goto err; + } + + mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, pool_fd, 0); + if (mmapped == MAP_FAILED) { + LOG_ERR("failed to mmap SHM backing memory file"); + goto err; + } + + pool = wl_shm_create_pool(shm, pool_fd, size); + if (pool == NULL) { + LOG_ERR("failed to create SHM pool"); + goto err; + } + + buf = wl_shm_pool_create_buffer( + pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); + if (buf == NULL) { + LOG_ERR("failed to create SHM buffer"); + goto err; + } + + /* We use the entire pool for our single buffer */ + wl_shm_pool_destroy(pool); pool = NULL; + close(pool_fd); pool_fd = -1; + + /* Create a cairo surface around the mmapped memory */ + cairo_surface = cairo_image_surface_create_for_data( + mmapped, CAIRO_FORMAT_ARGB32, width, height, stride); + if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) { + LOG_ERR("failed to create cairo surface: %s", + cairo_status_to_string(cairo_surface_status(cairo_surface))); + goto err; + } + + cairo = cairo_create(cairo_surface); + if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) { + LOG_ERR("failed to create cairo context: %s", + cairo_status_to_string(cairo_status(cairo))); + goto err; + } + + /* Push to list of available buffers, but marked as 'busy' */ + tll_push_back( + buffers, + ((struct buffer){ + .width = width, + .height = height, + .busy = true, + .size = size, + .mmapped = mmapped, + .wl_buf = buf, + .cairo_surface = cairo_surface, + .cairo = cairo} + ) + ); + + struct buffer *ret = &tll_back(buffers); + wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret); + return ret; + +err: + if (cairo != NULL) + cairo_destroy(cairo); + if (cairo_surface != NULL) + cairo_surface_destroy(cairo_surface); + if (buf != NULL) + wl_buffer_destroy(buf); + if (pool != NULL) + wl_shm_pool_destroy(pool); + if (pool_fd != -1) + close(pool_fd); + if (mmapped != NULL) + munmap(mmapped, size); + + return NULL; +} + +void +shm_fini(void) +{ + tll_foreach(buffers, it) { + struct buffer *buf = &it->item; + + cairo_destroy(buf->cairo); + cairo_surface_destroy(buf->cairo_surface); + wl_buffer_destroy(buf->wl_buf); + munmap(buf->mmapped, buf->size); + + tll_remove(buffers, it); + } +} diff --git a/shm.h b/shm.h new file mode 100644 index 00000000..35360db6 --- /dev/null +++ b/shm.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include +#include + +struct buffer { + int width; + int height; + + bool busy; + size_t size; + void *mmapped; + + struct wl_buffer *wl_buf; + + cairo_surface_t *cairo_surface; + cairo_t *cairo; +}; + +struct buffer *shm_get_buffer(struct wl_shm *shm, int width, int height); +void shm_fini(void); diff --git a/tllist.h b/tllist.h new file mode 100644 index 00000000..acd0ab47 --- /dev/null +++ b/tllist.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include + +#define TLL_PASTE2( a, b) a##b +#define TLL_PASTE( a, b) TLL_PASTE2( a, b) + +/* Utility macro to generate a list element struct with a unique struct tag */ +#define TLL_UNIQUE_INNER_STRUCT(TYPE, ID) \ + struct TLL_PASTE(__tllist_ , ID) { \ + TYPE item; \ + struct TLL_PASTE(__tllist_, ID) *prev; \ + struct TLL_PASTE(__tllist_, ID) *next; \ + } *head, *tail; + +/* + * Defines a new typed-list type, or directly instantiate a typed-list variable + * + * Example a, declare a variable (list of integers): + * tll(int) my_list; + * + * Example b, declare a type, and then use the type: + * tll(int, my_list_type); + * struct my_list_type my_list; + */ +#define tll(TYPE, ...) \ + struct __VA_ARGS__ { \ + TLL_UNIQUE_INNER_STRUCT(TYPE, __COUNTER__) \ + size_t length; \ + } + +/* Initializer: tll(int) my_list = tll_init(); */ +#define tll_init() {.head = NULL, .tail = NULL, .length = 0} + +/* Length/size of list: printf("size: %zu\n", tll_length(my_list)); */ +#define tll_length(list) (list).length + +/* Adds a new item to the back of the list */ +#define tll_push_back(list, new_item) \ + do { \ + __typeof__((list).head) __e = malloc(sizeof(*__e)); \ + __e->item = (new_item); \ + __e->prev = (list).tail; \ + __e->next = NULL; \ + if ((list).head == NULL) \ + (list).head = (list).tail = __e; \ + else { \ + (list).tail->next = __e; \ + (list).tail = __e; \ + } \ + (list).length++; \ + } while (0) + +/* Adds a new item to the front of the list */ +#define tll_push_front(list, new_item) \ + do { \ + __typeof__((list).head) __e = malloc(sizeof(*__e)); \ + __e->item = (new_item); \ + __e->prev = NULL; \ + __e->next = (list).head; \ + if ((list).head == NULL) \ + (list).head = (list).tail = __e; \ + else { \ + (list).head->prev = __e; \ + (list).head = __e; \ + } \ + (list).length++; \ + } while (0) + + +/* + * Iterates the list. is an iterator pointer. You can access the + * list item with ->item: + * + * tll(int) my_list = vinit(); + * tll_push_back(my_list, 5); + * + * tll_foreach(my_list i) { + * printf("%d\n", i->item); + * } +*/ +#define tll_foreach(list, it) \ + for (__typeof__(*(list).head) *it = (list).head, \ + *it_next = it != NULL ? it->next : NULL; \ + it != NULL; \ + it = it_next, \ + it_next = it_next != NULL ? it_next->next : NULL) + +/* Same as tll_foreach(), but iterates backwards */ +#define tll_rforeach(list, it) \ + for (__typeof__(*(list).tail) *it = (list).tail, \ + *it_prev = it != NULL ? it->prev : NULL; \ + it != NULL; \ + it = it_prev, \ + it_prev = it_prev != NULL ? it_prev->prev : NULL) + +/* + * Removes an entry from the list. is an iterator. I.e. you can + * only call this from inside a tll_foreach() or tll_rforeach() loop. + */ +#define tll_remove(list, it) \ + do { \ + assert((list).length > 0); \ + __typeof__((list).head) __prev = it->prev; \ + __typeof__((list).head) __next = it->next; \ + if (__prev != NULL) \ + __prev->next = __next; \ + else \ + (list).head = __next; \ + if (__next != NULL) \ + __next->prev = __prev; \ + else \ + (list).tail = __prev; \ + free(it); \ + (list).length--; \ + } while (0) + +/* Same as tll_remove(), but calls free_callback(it->item) */ +#define tll_remove_and_free(list, it, free_callback) \ + do { \ + free_callback((it)->item); \ + tll_remove((list), (it)); \ + } while (0) + +#define tll_front(list) (list).head->item +#define tll_back(list) (list).tail->item + +/* + * Removes the first element from the list, and returns it (note: + * returns the *actual* item, not an iterator. + */ +#define tll_pop_front(list) \ + ({__typeof__((list).head) it = (list).head; \ + __typeof__((list).head->item) __ret = it->item; \ + tll_remove((list), it); \ + __ret; \ + }) + +/* Same as tll_pop_front(), but returns/removes the *last* element */ +#define tll_pop_back(list) \ + ({__typeof__((list).tail) it = (list).tail; \ + __typeof__((list).tail->item) __ret = it->item; \ + tll_remove((list), it); \ + __ret; \ + }) + +/* Frees the list. This call is *not* needed if the list is already empty. */ +#define tll_free(list) \ + tll_foreach(list, __it) \ + tll_remove(list, __it) + +/* Same as tll_free(), but also calls free_callback(item) for every item */ +#define tll_free_and_free(list, free_callback) \ + tll_foreach(list, __it) \ + tll_remove_and_free(list, __it, free_callback)