From 85e5323ee1d1c9dd7f0c6be39051309d4bbbe9e4 Mon Sep 17 00:00:00 2001 From: tocariimaa Date: Fri, 17 Jan 2025 00:32:12 -0300 Subject: [PATCH] move wayland experiments here --- audiovisual/wayland/README.md | 19 ++ audiovisual/wayland/client.c | 512 +++++++++++++++++++++++++++++++ audiovisual/wayland/dbgpri.h | 33 ++ audiovisual/wayland/meson.build | 42 +++ audiovisual/wayland/shm_helper.c | 53 ++++ audiovisual/wayland/shm_helper.h | 3 + 6 files changed, 662 insertions(+) create mode 100644 audiovisual/wayland/README.md create mode 100644 audiovisual/wayland/client.c create mode 100644 audiovisual/wayland/dbgpri.h create mode 100644 audiovisual/wayland/meson.build create mode 100644 audiovisual/wayland/shm_helper.c create mode 100644 audiovisual/wayland/shm_helper.h diff --git a/audiovisual/wayland/README.md b/audiovisual/wayland/README.md new file mode 100644 index 0000000..3572ea0 --- /dev/null +++ b/audiovisual/wayland/README.md @@ -0,0 +1,19 @@ +# Wayland experiments +Just a bunch of programs I wrote to teach myself about Wayland using the [Wayland book](https://wayland-book.com/) as reference. + +Building this requires [Meson](https://mesonbuild.com/). +The build script will take care of creating the interfacing and glue code for you. + +``` +meson setup --buildtype=release build +meson compile -C build +``` + +## client.c +A Wayland client that draws a barberpole-like animation; implementing the XDG shell protocol, interfaces with +`wl_seat` and `wl_keyboard` too, so it will listen keyboard events and print them on the console. +Because of this, libxkbcommon is required. + +You can close it by pressing the escape key or just how you would close any window (see below). + +This client also implements the XDG decoration protocol and uses server-side decorations. diff --git a/audiovisual/wayland/client.c b/audiovisual/wayland/client.c new file mode 100644 index 0000000..4e7275f --- /dev/null +++ b/audiovisual/wayland/client.c @@ -0,0 +1,512 @@ +// Wayland client example, made following the https://wayland-book.com/ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "shm_helper.h" +#include "xdg-shell-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" +#define _DBGPRINT_ +#include "dbgpri.h" + +#define COMPOSITOR_INTERFACE_VER 4u +#define TOPLEVEL_TITLE "Fubuland" +#define TOPLEVEL_APP_ID "net.tocariimaa.fubuland" + +struct ClientState +{ + struct wl_display *display; + struct wl_compositor *compositor; + struct wl_registry *registry; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_keyboard *keyboard; + struct xdg_wm_base *xdg_wm_base; + + struct wl_surface *wl_surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zxdg_decoration_manager_v1 *xdg_decoration_mgr; + struct zxdg_toplevel_decoration_v1 *xdg_toplevel_deco; + + float offset; + uint32_t last_frame; + int32_t width, height; + bool closed; + + struct { + struct wl_buffer *buffer; + uint32_t *pool_data; + size_t pool_size; + } memory; + struct { + struct xkb_context *ctx; + struct xkb_state *state; + struct xkb_keymap *keymap; + } xkb; +}; + +static inline void draw_frame(struct ClientState *state); +static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time); + +static inline void +draw_frame(struct ClientState *state) +{ +// Macro helper to index the 1D buffer as a 2D lattice (buffer 2D index) +#define b2di(data, width, y, x) (data[y * width + x]) + const int bar1_color = 0xe7f5c5; + const int bar2_color = 0xa2dd99; + const int bar1_thickness = 64; // px + const int bar2_thickness = 64; + + int offset = (int)state->offset % bar2_thickness; + for (int y = 0; y < state->height; ++y) { + for (int x = 0; x < state->width; ++x) { + if (((x + offset) + (y + offset)) % bar1_thickness < bar2_thickness / 2) + b2di(state->memory.pool_data, state->width, y, x) = bar1_color; + else + b2di(state->memory.pool_data, state->width, y, x) = bar2_color; + } + } +#undef b2di +} + +static void +create_memory_pool(struct ClientState *state) +{ + int width = state->width, height = state->height; + int stride = width * 4; // 4 bytes per pixel; for WL_SHM_FORMAT_XRGB8888 + int pool_size = height * stride; + const int32_t offset = 0; + + int shm_fd; + if ((shm_fd = allocate_shm_file(pool_size)) < 0) + err(EXIT_FAILURE, "couldn't create shared memory object"); + + uint32_t *pool_data = mmap(NULL, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + if (pool_data == MAP_FAILED) { + warn("mmap failed"); + close(shm_fd); + return; + } +#ifdef __linux__ + // Request the kernel to use hugepages for shared memory if possible, improves performance by reducing page faults. + // This only will work (on Linux) if transparent hugepages are enabled for shmem on your system. + // To see/enable this check /sys/kernel/mm/transparent_hugepage/shmem_enabled. + // Related: https://stackoverflow.com/questions/73278608/can-mmaps-performance-be-improved-for-shared-memory + if (madvise(pool_data, pool_size, MADV_HUGEPAGE) < 0) + warn("madvise hugepage"); +#endif + // Might help... + if (madvise(pool_data, pool_size, MADV_WILLNEED) < 0) + warn("madvise willneed"); + + //dbgpri("w: %d h: %d\n", state->width, state->height); + struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, shm_fd, pool_size); + struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, offset, width, height, stride, WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + close(shm_fd); + + state->memory.pool_data = pool_data; + state->memory.pool_size = pool_size; + state->memory.buffer = buffer; +} + +/* +static void +buffer_release(void *data, struct wl_buffer *wl_buf) +{ + (void)data; + dbgpri("buffer release event"); + wl_buffer_destroy(wl_buf); +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_release, +}; +*/ + +static void +destroy_memory_pool(struct ClientState *state) +{ + munmap(state->memory.pool_data, state->memory.pool_size); + state->memory.pool_data = NULL; + wl_buffer_destroy(state->memory.buffer); + /* This event never gets called for some reason, leaking memory, so I just call wl_buffer_destroy manually */ + /* wl_buffer_add_listener(state->memory.buffer, &buffer_listener, NULL); */ +} + +static void +xdg_toplevel_configure(void *data, struct xdg_toplevel *xdgtpl, int32_t width, int32_t height, struct wl_array *states) +{ + (void)xdgtpl; + // the compositor is asking us to give it an initial size + if (width == 0 || height == 0) + return; + + struct ClientState *state = data; + state->width = width; + state->height = height; + dbgpri("XDG toplevel configure: "); + + enum xdg_toplevel_state *xdg_state; + wl_array_for_each(xdg_state, states) { + putchar('\t'); + switch (*xdg_state) { + case XDG_TOPLEVEL_STATE_MAXIMIZED: + puts("maximized"); + break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: + puts("fullscreen"); + break; + case XDG_TOPLEVEL_STATE_RESIZING: + puts("resizing"); + break; + case XDG_TOPLEVEL_STATE_ACTIVATED: + puts("activated"); + break; + default: + printf("xdgstate: %d\n", *xdg_state); + } + } +} + +static void +xdg_toplevel_close(void *data, struct xdg_toplevel *xdgtpl) +{ + (void)xdgtpl; + ((struct ClientState *)data)->closed = true; +} + +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + struct ClientState *state = data; + // Acknowledge to the server that we've configured the surface + xdg_surface_ack_configure(xdg_surface, serial); + dbgpri("XDG surface configure event"); + + if (state->memory.pool_data != NULL) + destroy_memory_pool(state); + // The surface has a new size now, so create a new memory pool with the proper size + // TODO: we can optimize for the case of the new surface size being smaller than the current + // buffer, avoiding creating a new buffer. + create_memory_pool(state); + + // We have to draw here as well, otherwise we will send an empty frame + draw_frame(state); + wl_surface_attach(state->wl_surface, state->memory.buffer, 0, 0); + wl_surface_commit(state->wl_surface); +} + +// Reply to a ping event from the compositor, so it does not think that we're dead. +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + (void)data; + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, +}; + +static const struct wl_callback_listener wl_surface_frame_listener = { + .done = surface_frame_done, +}; + +// This function will be called each time the server requests a frame to be drawn +static void +surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) +{ + struct ClientState *state = data; + + wl_callback_destroy(cb); + // Add this function itself to be called on the next frame event + cb = wl_surface_frame(state->wl_surface); + wl_callback_add_listener(cb, &wl_surface_frame_listener, state); + + if (state->last_frame != 0) { + int elapsed = time - state->last_frame; + state->offset += elapsed / 1000.0 * 60; // move 60px per second + } + + draw_frame(state); + wl_surface_attach(state->wl_surface, state->memory.buffer, 0, 0); + // To "damage" a surface means to update it + wl_surface_damage_buffer(state->wl_surface, 0, 0, state->width, state->height); + // We're done with this frame, send it + wl_surface_commit(state->wl_surface); + state->last_frame = time; +} + +static void +keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) +{ + (void)keyboard; + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + struct ClientState *state = data; + + // Release previous xkb state, if any + xkb_keymap_unref(state->xkb.keymap); + xkb_state_unref(state->xkb.state); + + // must be mapped as MAP_PRIVATE since wl_keyboard version 7 + char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map_shm == MAP_FAILED) { + warn("mmap keyboard keymap"); + return; + } + state->xkb.keymap = xkb_keymap_new_from_string( + state->xkb.ctx, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_shm, size); + close(fd); + state->xkb.state = xkb_state_new(state->xkb.keymap); +} + +static void +keyboard_decode_key(struct ClientState *state, uint32_t keycode, uint32_t kstate) +{ + const char *key_state = kstate == WL_KEYBOARD_KEY_STATE_PRESSED ? "press" : "release"; + char key_name[255], keyutf8[255]; + + xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb.state, keycode); + xkb_keysym_get_name(sym, key_name, sizeof(key_name)); + xkb_state_key_get_utf8(state->xkb.state, keycode, keyutf8, sizeof(keyutf8)); + dbgpri("Key %s: name: %s; code: %d; utf8: %s", key_state, key_name, sym, keyutf8); + + if (sym == XKB_KEY_Escape) + state->closed = true; +} + +static void +keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ + (void)keyboard, (void)serial, (void)surface; + dbgpri("Got keyboard enter event"); + uint32_t *key; + wl_array_for_each(key, keys) { + keyboard_decode_key(data, *key + 8, 0); + } +} + +static void +keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) +{ + dbgpri("Keyboard focus leave event"); + (void)data, (void)keyboard, (void)serial, (void)surface; +} + +static void +keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, + uint32_t key, uint32_t kstate) +{ + (void)keyboard, (void)serial, (void)time; + + const uint32_t keycode = key + 8; // evdev codes to xkb codes + keyboard_decode_key(data, keycode, kstate); +} + +static void +keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mod_pressed, + uint32_t mod_latchd, uint32_t mod_lockd, uint32_t group) +{ + (void)serial, (void)keyboard; + struct ClientState *state = data; + xkb_state_update_mask(state->xkb.state, mod_pressed, mod_latchd, mod_lockd, + 0, 0, group); +} + +static void +keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) +{ + (void)data, (void)keyboard, (void)rate, (void)delay; +} + +static const struct wl_keyboard_listener wl_keyboard_listener = { + .keymap = keyboard_keymap, + .enter = keyboard_enter, + .leave = keyboard_leave, + .key = keyboard_key, + .modifiers = keyboard_modifiers, + .repeat_info = keyboard_repeat_info, +}; + +static void +seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities) +{ + struct ClientState *state = data; + bool keycap = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + if (keycap && state->keyboard == NULL) { + dbgpri("we have a keyboard"); + state->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(state->keyboard, &wl_keyboard_listener, data); + } else if (!keycap && state->keyboard != NULL) { + wl_keyboard_release(state->keyboard); + state->keyboard = NULL; + } + if (capabilities & WL_SEAT_CAPABILITY_POINTER) + dbgpri("we have a pointer"); + if (capabilities & WL_SEAT_CAPABILITY_TOUCH) + dbgpri("we have a touchscreen"); +} + +static void +seat_name(void *data, struct wl_seat *seat, const char *name) +{ + (void)data, (void)seat; + dbgpri("seat id: %s", name); +} + +static const struct wl_seat_listener wl_seat_listener = { + .capabilities = seat_capabilities, + .name = seat_name, +}; + +void xdg_toplevel_deco_cfg(void *data, struct zxdg_toplevel_decoration_v1 *deco, uint32_t mode) +{ + dbgpri("Deco cfg: %d", mode); + /* We're supposed to send an ack_configure, however, where do we get the serial? + * xdg_surface_ack_configure(((struct ClientState *)data)->xdg_surface, ?); + */ +} + +static const struct zxdg_toplevel_decoration_v1_listener xdg_deco_listener = { + .configure = xdg_toplevel_deco_cfg, +}; + +// On this callback we bind objects with an interface +void reg_handle_global(void *data, struct wl_registry *reg, uint32_t name, const char *interface, uint32_t ver) +{ + (void)ver; + struct ClientState *state = data; + // dbgpri("Interface: '%s': ver.: %u, name: %u", interface, ver, name); + if (strcmp(interface, wl_compositor_interface.name) == 0) { + dbgpri("Bound compositor interface: %s", interface); + state->compositor = wl_registry_bind(reg, name, &wl_compositor_interface, COMPOSITOR_INTERFACE_VER); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + dbgpri("Bound shared memory interface"); + state->shm = wl_registry_bind(reg, name, &wl_shm_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + dbgpri("Bound XDG WM base interface"); + state->xdg_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + dbgpri("Bound seat interface"); + state->seat = wl_registry_bind(reg, name, &wl_seat_interface, 8); + wl_seat_add_listener(state->seat, &wl_seat_listener, state); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + dbgpri("Bound XDG decoration protocol"); + state->xdg_decoration_mgr = wl_registry_bind(reg, name, &zxdg_decoration_manager_v1_interface, 1); + } +} + +void reg_handle_global_rm(void *data, struct wl_registry *reg, uint32_t name) +{ + (void)data, (void)reg, (void)name; + dbgpri("unbind event: %u", name); +} + +static const struct wl_registry_listener reg_listener = { + .global = reg_handle_global, // object bind event + .global_remove = reg_handle_global_rm, // '' removal '' +}; + +int main() +{ + struct ClientState state = {.width = 640, .height = 480}; + + state.display = wl_display_connect(NULL); + if (state.display == NULL) + errx(EXIT_FAILURE, "could not open display"); + + dbgpri("Connected to Wayland server; display fd: %d", wl_display_get_fd(state.display)); + + // get the object registry + state.registry = wl_display_get_registry(state.display); + wl_registry_add_listener(state.registry, ®_listener, &state); + // Do a roundtrip, block until the server replies to all of our requests + wl_display_roundtrip(state.display); + + if (state.compositor == NULL || state.shm == NULL || state.registry == NULL) + errx(EXIT_FAILURE, "Failed to retrieve essential interfaces (comp shm reg)"); + + if ((state.xkb.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS)) == NULL) + errx(EXIT_FAILURE, "xkb_context_new"); + + state.wl_surface = wl_compositor_create_surface(state.compositor); + state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.wl_surface); + xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state); + state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface); + state.xdg_toplevel_deco = zxdg_decoration_manager_v1_get_toplevel_decoration( + state.xdg_decoration_mgr, state.xdg_toplevel + ); + + /* Set up the toplevel decoration listener. + * Note that this must be done before the setup of the xdg_toplevel attaches any + * buffer to it. */ + zxdg_toplevel_decoration_v1_add_listener( + state.xdg_toplevel_deco, + &xdg_deco_listener, + &state + ); + xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, &state); + xdg_toplevel_set_title(state.xdg_toplevel, TOPLEVEL_TITLE); + xdg_toplevel_set_app_id(state.xdg_toplevel, TOPLEVEL_APP_ID); + /* Tell the compositor to add decorations for us */ + zxdg_toplevel_decoration_v1_set_mode(state.xdg_toplevel_deco, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + + struct wl_callback *cb = wl_surface_frame(state.wl_surface); + // Bootstrap the frame drawing event sequence + wl_callback_add_listener(cb, &wl_surface_frame_listener, &state); + wl_surface_commit(state.wl_surface); + + // Event dispatch loop + while (!state.closed && wl_display_dispatch(state.display) != -1) { + } + + dbgpri("Closing connection and destroying objects"); + /* Clean up */ + xkb_keymap_unref(state.xkb.keymap); + xkb_state_unref(state.xkb.state); + xkb_context_unref(state.xkb.ctx); + destroy_memory_pool(&state); + zxdg_decoration_manager_v1_destroy(state.xdg_decoration_mgr); + zxdg_toplevel_decoration_v1_destroy(state.xdg_toplevel_deco); + xdg_toplevel_destroy(state.xdg_toplevel); + xdg_surface_destroy(state.xdg_surface); + xdg_wm_base_destroy(state.xdg_wm_base); + wl_seat_destroy(state.seat); + wl_surface_destroy(state.wl_surface); + wl_registry_destroy(state.registry); + wl_shm_destroy(state.shm); + wl_compositor_destroy(state.compositor); + wl_display_disconnect(state.display); + + return 0; +} diff --git a/audiovisual/wayland/dbgpri.h b/audiovisual/wayland/dbgpri.h new file mode 100644 index 0000000..0a6c567 --- /dev/null +++ b/audiovisual/wayland/dbgpri.h @@ -0,0 +1,33 @@ +#ifndef _dbpri_h_ +#define _dbpri_h_ + +#include +#include +#include + +#ifdef _DBGPRINT_ + #define dbgpri(...) dbgpri_impl(__VA_ARGS__) +#else + #define dbgpri(...) +#endif + +static void dbgpri_impl(const char *fmt, ...) +{ + va_list args; + struct timespec now; + char msg[2048]; + + va_start(args, fmt); + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + vsnprintf(msg, sizeof(msg), fmt, args); + fprintf(stderr, "[%ld.%-9ld] %s\n", now.tv_sec, now.tv_nsec, msg); + + va_end(args); +} + +static void dbgput(const char *str) +{ + fprintf(stderr, "%s\n", str); +} + +#endif diff --git a/audiovisual/wayland/meson.build b/audiovisual/wayland/meson.build new file mode 100644 index 0000000..7b2dda9 --- /dev/null +++ b/audiovisual/wayland/meson.build @@ -0,0 +1,42 @@ +project('wayland-client-test', 'c', default_options: ['warning_level=2']) + +wl_scanner = dependency('wayland-scanner', native: true) +wl_scanner_prog = find_program(wl_scanner.get_variable('wayland_scanner'), native: true) +wl_scanner_pcode = generator( + wl_scanner_prog, + output: '@BASENAME@-protocol.c', + arguments: ['private-code', '@INPUT@', '@OUTPUT@'] +) +wl_scanner_client = generator( + wl_scanner_prog, + output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@', '@OUTPUT@'] +) + +wl_stable_proto_dir = '/usr/share/wayland-protocols/stable/' +wl_unstable_proto_dir = '/usr/share/wayland-protocols/unstable/' +wl_protocols = [ + wl_stable_proto_dir / 'xdg-shell/xdg-shell.xml', + wl_unstable_proto_dir / 'xdg-decoration/xdg-decoration-unstable-v1.xml' +] + +wl_protocol_srcs = [] + +foreach filename : wl_protocols + wl_protocol_srcs += wl_scanner_pcode.process(filename) + wl_protocol_srcs += wl_scanner_client.process(filename) +endforeach + + +if get_option('buildtype') == 'release' + add_project_arguments('-march=native', '-fno-plt', '-flto', language: 'c') +endif + +cc = meson.get_compiler('c') +deps = [ + cc.find_library('rt', required: false), # for shm_* functions + dependency('wayland-client'), + dependency('xkbcommon'), +] +srcs = ['client.c', 'shm_helper.c', wl_protocol_srcs] +executable('client', srcs, dependencies: deps) diff --git a/audiovisual/wayland/shm_helper.c b/audiovisual/wayland/shm_helper.c new file mode 100644 index 0000000..aef3fea --- /dev/null +++ b/audiovisual/wayland/shm_helper.c @@ -0,0 +1,53 @@ +// taken from wayland-book.com (public domain) +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include + +static void +randname(char *buf) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int +create_shm_file(void) +{ + int retries = 100; + do { + char name[] = "/wl_shm-XXXXXX"; + randname(name + sizeof(name) - 7); + --retries; + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + return -1; +} + +int +allocate_shm_file(size_t size) +{ + int fd = create_shm_file(); + if (fd < 0) + return -1; + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + return fd; +} diff --git a/audiovisual/wayland/shm_helper.h b/audiovisual/wayland/shm_helper.h new file mode 100644 index 0000000..5a3e6fb --- /dev/null +++ b/audiovisual/wayland/shm_helper.h @@ -0,0 +1,3 @@ +#include + +int allocate_shm_file(size_t size);