commit 910c540ea912c70cba733d24264abf01cbae6019 Author: Daniel Eklöf Date: Wed Jun 12 20:08:54 2019 +0200 initial commit: maps an XDG toplevel window 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)