initial commit: maps an XDG toplevel window

This commit is contained in:
Daniel Eklöf 2019-06-12 20:08:54 +02:00
commit 910c540ea9
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
8 changed files with 874 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/bld

146
log.c Normal file
View file

@ -0,0 +1,146 @@
#include "log.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <syslog.h>
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);
}

35
log.h Normal file
View file

@ -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

286
main.c Normal file
View file

@ -0,0 +1,286 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <poll.h>
#include <wayland-client.h>
#include <xdg-shell.h>
#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, &registry_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;
}

62
meson.build Normal file
View file

@ -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)

163
shm.c Normal file
View file

@ -0,0 +1,163 @@
#include "shm.h"
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <linux/memfd.h>
#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);
}
}

24
shm.h Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <cairo.h>
#include <wayland-client.h>
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);

157
tllist.h Normal file
View file

@ -0,0 +1,157 @@
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>
#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. <it> 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. <it> 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)