do not edit — generated by btf.
git.druid.rocksindexmilitantprogrammerastral_canvassrc/platform/wayland.c

src/platform/wayland.c


#include "platform.h"
#include "../input/input.h"
#include "shm.h"
 
#include "xdg-shell-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
 
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
 
#include <wayland-client.h>
#include <wayland-cursor.h>
 
#define VK_USE_PLATFORM_WAYLAND_KHR
#include <vulkan/vulkan.h>
 
/*
 * Number of wl_shm buffers in the present swap chain. Two is enough for
 * double-buffering: the compositor holds one (busy) while we fill the
 * other from staging.
 */
enum { WL_BUF_COUNT = 2 };
 
/*
 * One wl_shm buffer in the swap chain. data is the mmap of the pool; the
 * compositor sets busy via the wl_buffer.release listener. present()
 * copies staging into a free buffer, marks it busy, and attaches it.
 */
typedef struct WlBuf {
    struct wl_buffer *buffer; /* wl_shm XRGB8888 buffer; NULL if not allocated */
    void *data;               /* mmap of the shm pool; size bytes */
    size_t size;              /* width * height * 4 */
    int width, height;        /* buffer pixels (logical size × scale) */
    bool busy;                /* true while the compositor still holds this buffer */
} WlBuf;
 
/* Incomplete type so WlOutput can point at the backend before the full struct. */
typedef struct Wayland Wayland;
 
/*
 * One wl_output advertised by the registry. Scale is applied from the
 * outputs the surface has entered; current_scale() takes the max among
 * those with entered == true.
 */
typedef struct {
    Wayland *wayland;               /* back-pointer; output listeners call maybe_resize() on this */
    struct wl_output *output; /* bound wl_output (version 2 when available, for .scale) */
    int32_t scale;            /* integer buffer scale factor from wl_output.scale */
    bool entered;             /* true while our surface is on this output */
} WlOutput;
 
/*
 * Wayland backend, stored in Platform.impl. Owns the display connection,
 * xdg-shell toplevel, double-buffered shm swap chain, a CPU staging
 * buffer (what Platform.pixels points at), seat devices, and client-side
 * key repeat.
 *
 * Drawing goes staging → free WlBuf → attach/damage/commit. Integer
 * wl_surface.set_buffer_scale is the max scale of entered outputs.
 * Pointer events are coalesced into ptr until wl_pointer.frame (or
 * flushed immediately on older pointer versions).
 */
struct Wayland {
    Platform *platform;
    struct wl_display *display;
    struct wl_registry *registry;
    struct wl_compositor *compositor;
    struct wl_shm *shm;
    struct wl_seat *seat;
    struct wl_pointer *pointer;
    struct wl_keyboard *keyboard;
    struct xdg_wm_base *xdgWmBase;
    struct zxdg_decoration_manager_v1 *decorationManager;
    struct zxdg_toplevel_decoration_v1 *decoration;
    struct wl_surface *surface;
    struct xdg_surface *xdgSurface;
    struct xdg_toplevel *xdgToplevel;
 
    struct wl_cursor_theme *cursorTheme;
    struct wl_cursor *cursor;
    struct wl_surface *cursorSurface;
    uint32_t cursorSerial;
 
    WlOutput outputs[8];
    int outputCount;
 
    WlBuf bufs[WL_BUF_COUNT];
    uint32_t *staging;
    size_t stagingSize;
 
    bool configured;
    int pendingWidth, pendingHeight;
    int pendingScale;
 
    InputXkb xkb;
 
    struct {
        bool active;
        xkb_keycode_t key;
        int delayMilliseconds;
        int rate;
        long nextMilliseconds;
    } repeat;
 
    struct {
        bool haveMotion, haveButton, haveAxis, haveEnter, haveLeave;
        double x, y;
        uint32_t time;
        uint32_t button;
        bool pressed;
        double axisVertical, axisHorizontal;
        int32_t stepsVertical, stepsHorizontal;
        int32_t accum120Vertical, accum120Horizontal;
        uint32_t axisSource;
        bool haveAxisVertical, haveAxisHorizontal;
    } ptr;
};
 
static long now_ms(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
}
 
static void wl_push(Wayland *wayland, Event ev)
{
    eventQueuePush(&wayland->platform->events, ev);
}
 
static void buffer_release(void *data, struct wl_buffer *buffer)
{
    WlBuf *b = data;
    (void)buffer;
    b->busy = false;
}
 
static const struct wl_buffer_listener buffer_listener = {
    .release = buffer_release,
};
 
static void destroy_wl_buf(WlBuf *b)
{
    if (b->buffer)
        wl_buffer_destroy(b->buffer);
    shm_free(-1, b->data, b->size);
    memset(b, 0, sizeof(*b));
}
 
static int create_wl_buf(Wayland *wayland, WlBuf *b, int width, int height)
{
    destroy_wl_buf(b);
    if (width < 1)
        width = 1;
    if (height < 1)
        height = 1;
    int stride = width * 4;
    size_t size = (size_t)stride * (size_t)height;
    void *data = NULL;
    int fd = shm_alloc(size, &data);
    if (fd < 0)
        return -1;
    struct wl_shm_pool *pool = wl_shm_create_pool(wayland->shm, fd, (int32_t)size);
    close(fd);
    if (!pool) {
        shm_free(-1, data, size);
        return -1;
    }
    b->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
    wl_shm_pool_destroy(pool);
    if (!b->buffer) {
        shm_free(-1, data, size);
        return -1;
    }
    b->data = data;
    b->size = size;
    b->width = width;
    b->height = height;
    b->busy = false;
    wl_buffer_add_listener(b->buffer, &buffer_listener, b);
    return 0;
}
 
static int apply_size(Wayland *wayland, int width, int height, int scale)
{
    if (width < 1)
        width = 1;
    if (height < 1)
        height = 1;
    if (scale < 1)
        scale = 1;
 
    int buf_w = width * scale;
    int buf_h = height * scale;
    size_t size = (size_t)buf_w * (size_t)buf_h * 4;
    uint32_t *staging = calloc(1, size);
    if (!staging)
        return -1;
 
    for (int i = 0; i < WL_BUF_COUNT; i++) {
        if (create_wl_buf(wayland, &wayland->bufs[i], buf_w, buf_h) < 0) {
            free(staging);
            return -1;
        }
    }
 
    free(wayland->staging);
    wayland->staging = staging;
    wayland->stagingSize = size;
    wayland->platform->pixels = staging;
    wayland->platform->width = width;
    wayland->platform->height = height;
    wayland->platform->scale = scale;
    wayland->platform->bufferWidth = buf_w;
    wayland->platform->bufferHeight = buf_h;
    wayland->platform->stride = buf_w * 4;
    /* Vulkan WSI attaches its own buffers; buffer_scale would fight the ICD. */
    if (!wayland->platform->gpuPresent)
        wl_surface_set_buffer_scale(wayland->surface, scale);
    return 0;
}
 
static int current_scale(Wayland *wayland)
{
    int scale = 0;
    for (int i = 0; i < wayland->outputCount; i++) {
        if (wayland->outputs[i].entered && wayland->outputs[i].scale > scale)
            scale = wayland->outputs[i].scale;
    }
    return scale > 0 ? scale : (wayland->platform->scale > 0 ? wayland->platform->scale : 1);
}
 
static void maybe_resize(Wayland *wayland)
{
    if (!wayland->surface)
        return;
    int width = wayland->pendingWidth > 0 ? wayland->pendingWidth : wayland->platform->width;
    int height = wayland->pendingHeight > 0 ? wayland->pendingHeight : wayland->platform->height;
    int scale = current_scale(wayland);
    if (width == wayland->platform->width && height == wayland->platform->height && scale == wayland->platform->scale)
        return;
    if (apply_size(wayland, width, height, scale) < 0)
        return;
    Event ev = { .kind = EVENT_RESIZE };
    ev.resize.width = wayland->platform->width;
    ev.resize.height = wayland->platform->height;
    ev.resize.scale = wayland->platform->scale;
    wl_push(wayland, ev);
}
 
static void set_cursor(Wayland *wayland, uint32_t serial)
{
    wayland->cursorSerial = serial;
    if (!wayland->pointer || !wayland->cursorSurface || !wayland->shm)
        return;
    if (!wayland->cursorTheme) {
        wayland->cursorTheme = wl_cursor_theme_load(NULL, 24, wayland->shm);
        if (!wayland->cursorTheme)
            return;
        wayland->cursor = wl_cursor_theme_get_cursor(wayland->cursorTheme, "xterm");
        if (!wayland->cursor)
            wayland->cursor = wl_cursor_theme_get_cursor(wayland->cursorTheme, "text");
        if (!wayland->cursor)
            wayland->cursor = wl_cursor_theme_get_cursor(wayland->cursorTheme, "left_ptr");
    }
    if (!wayland->cursor || wayland->cursor->image_count < 1)
        return;
    struct wl_cursor_image *img = wayland->cursor->images[0];
    struct wl_buffer *buf = wl_cursor_image_get_buffer(img);
    if (!buf)
        return;
    wl_pointer_set_cursor(wayland->pointer, serial, wayland->cursorSurface,
                          (int32_t)img->hotspot_x, (int32_t)img->hotspot_y);
    wl_surface_attach(wayland->cursorSurface, buf, 0, 0);
    wl_surface_damage(wayland->cursorSurface, 0, 0, (int32_t)img->width, (int32_t)img->height);
    wl_surface_commit(wayland->cursorSurface);
}
 
static bool pointer_frames(Wayland *wayland)
{
    return wayland->pointer && wl_pointer_get_version(wayland->pointer) >= 5;
}
 
static void emit_pointer(Wayland *wayland, PointerEvent pe)
{
    pe.buttons = wayland->platform->buttons;
    pe.modifiers = wayland->platform->modifiers;
    pe.x = wayland->platform->mouseX;
    pe.y = wayland->platform->mouseY;
    Event ev = { .kind = EVENT_POINTER };
    ev.pointer = pe;
    wl_push(wayland, ev);
}
 
static void flush_pointer_frame(Wayland *wayland)
{
    if (wayland->ptr.haveLeave) {
        wayland->platform->pointerInside = false;
        PointerEvent pe = { .kind = POINTER_LEAVE, .timeMilliseconds = wayland->ptr.time };
        emit_pointer(wayland, pe);
    }
    if (wayland->ptr.haveEnter) {
        wayland->platform->pointerInside = true;
        wayland->platform->mouseX = wayland->ptr.x;
        wayland->platform->mouseY = wayland->ptr.y;
        PointerEvent pe = { .kind = POINTER_ENTER, .timeMilliseconds = wayland->ptr.time };
        emit_pointer(wayland, pe);
        set_cursor(wayland, wayland->cursorSerial);
    }
    if (wayland->ptr.haveMotion) {
        wayland->platform->mouseX = wayland->ptr.x;
        wayland->platform->mouseY = wayland->ptr.y;
        PointerEvent pe = { .kind = POINTER_MOTION, .timeMilliseconds = wayland->ptr.time };
        emit_pointer(wayland, pe);
    }
    if (wayland->ptr.haveButton) {
        uint8_t mask = buttonMask(wayland->ptr.button);
        if (wayland->ptr.pressed)
            wayland->platform->buttons |= mask;
        else
            wayland->platform->buttons &= (uint8_t)~mask;
        PointerEvent pe = {
            .kind = POINTER_BUTTON,
            .timeMilliseconds = wayland->ptr.time,
            .button = wayland->ptr.button,
            .pressed = wayland->ptr.pressed,
        };
        emit_pointer(wayland, pe);
    }
    if (wayland->ptr.haveAxis) {
        if (wayland->ptr.haveAxisVertical) {
            PointerEvent pe = {
                .kind = POINTER_AXIS,
                .timeMilliseconds = wayland->ptr.time,
                .axis = AXIS_VERTICAL,
                .axisValue = wayland->ptr.axisVertical,
                .axisSteps = wayland->ptr.stepsVertical,
                .axisSource = wayland->ptr.axisSource,
            };
            emit_pointer(wayland, pe);
        }
        if (wayland->ptr.haveAxisHorizontal) {
            PointerEvent pe = {
                .kind = POINTER_AXIS,
                .timeMilliseconds = wayland->ptr.time,
                .axis = AXIS_HORIZONTAL,
                .axisValue = wayland->ptr.axisHorizontal,
                .axisSteps = wayland->ptr.stepsHorizontal,
                .axisSource = wayland->ptr.axisSource,
            };
            emit_pointer(wayland, pe);
        }
    }
    memset(&wayland->ptr, 0, sizeof(wayland->ptr));
    wayland->ptr.axisSource = AXIS_SOURCE_UNKNOWN;
}
 
static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial,
                          struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy)
{
    Wayland *wayland = data;
    (void)pointer;
    (void)surface;
    wayland->cursorSerial = serial;
    wayland->ptr.haveEnter = true;
    wayland->ptr.x = wl_fixed_to_double(sx);
    wayland->ptr.y = wl_fixed_to_double(sy);
    if (!pointer_frames(wayland))
        flush_pointer_frame(wayland);
}
 
static void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial,
                          struct wl_surface *surface)
{
    Wayland *wayland = data;
    (void)pointer;
    (void)serial;
    (void)surface;
    wayland->ptr.haveLeave = true;
    if (!pointer_frames(wayland))
        flush_pointer_frame(wayland);
}
 
static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time,
                           wl_fixed_t sx, wl_fixed_t sy)
{
    Wayland *wayland = data;
    (void)pointer;
    wayland->ptr.haveMotion = true;
    wayland->ptr.time = time;
    wayland->ptr.x = wl_fixed_to_double(sx);
    wayland->ptr.y = wl_fixed_to_double(sy);
    if (!pointer_frames(wayland))
        flush_pointer_frame(wayland);
}
 
static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial,
                           uint32_t time, uint32_t button, uint32_t state)
{
    Wayland *wayland = data;
    (void)pointer;
    (void)serial;
    uint32_t b = linuxButtonToButton(button);
    if (!b)
        return;
    wayland->ptr.haveButton = true;
    wayland->ptr.time = time;
    wayland->ptr.button = b;
    wayland->ptr.pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED);
    if (!pointer_frames(wayland))
        flush_pointer_frame(wayland);
}
 
static uint32_t map_axis_source(uint32_t src)
{
    switch (src) {
    case WL_POINTER_AXIS_SOURCE_WHEEL:      return AXIS_SOURCE_WHEEL;
    case WL_POINTER_AXIS_SOURCE_FINGER:     return AXIS_SOURCE_FINGER;
    case WL_POINTER_AXIS_SOURCE_CONTINUOUS: return AXIS_SOURCE_CONTINUOUS;
    default:                                return AXIS_SOURCE_UNKNOWN;
    }
}
 
static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time,
                         uint32_t axis, wl_fixed_t value)
{
    Wayland *wayland = data;
    (void)pointer;
    wayland->ptr.haveAxis = true;
    wayland->ptr.time = time;
    double v = wl_fixed_to_double(value);
    if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
        wayland->ptr.haveAxisVertical = true;
        wayland->ptr.axisVertical += v;
    } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
        wayland->ptr.haveAxisHorizontal = true;
        wayland->ptr.axisHorizontal += v;
    }
    if (!pointer_frames(wayland))
        flush_pointer_frame(wayland);
}
 
static void pointer_frame(void *data, struct wl_pointer *pointer)
{
    (void)pointer;
    flush_pointer_frame(data);
}
 
static void pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
{
    Wayland *wayland = data;
    (void)pointer;
    wayland->ptr.axisSource = map_axis_source(axis_source);
}
 
static void pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
{
    (void)data;
    (void)pointer;
    (void)time;
    (void)axis;
}
 
static void pointer_axis_discrete(void *data, struct wl_pointer *pointer,
                                  uint32_t axis, int32_t discrete)
{
    Wayland *wayland = data;
    (void)pointer;
    wayland->ptr.haveAxis = true;
    if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
        wayland->ptr.haveAxisVertical = true;
        wayland->ptr.stepsVertical += discrete;
    } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
        wayland->ptr.haveAxisHorizontal = true;
        wayland->ptr.stepsHorizontal += discrete;
    }
}
 
static void pointer_axis_value120(void *data, struct wl_pointer *pointer,
                                  uint32_t axis, int32_t value120)
{
    Wayland *wayland = data;
    (void)pointer;
    wayland->ptr.haveAxis = true;
    int32_t *acc = (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) ? &wayland->ptr.accum120Vertical
                                                             : &wayland->ptr.accum120Horizontal;
    int32_t *steps = (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) ? &wayland->ptr.stepsVertical
                                                               : &wayland->ptr.stepsHorizontal;
    if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
        wayland->ptr.haveAxisVertical = true;
    else
        wayland->ptr.haveAxisHorizontal = true;
    *acc += value120;
    while (*acc >= 120) {
        (*steps)++;
        *acc -= 120;
    }
    while (*acc <= -120) {
        (*steps)--;
        *acc += 120;
    }
}
 
static const struct wl_pointer_listener pointer_listener = {
    .enter = pointer_enter,
    .leave = pointer_leave,
    .motion = pointer_motion,
    .button = pointer_button,
    .axis = pointer_axis,
    .frame = pointer_frame,
    .axis_source = pointer_axis_source,
    .axis_stop = pointer_axis_stop,
    .axis_discrete = pointer_axis_discrete,
    .axis_value120 = pointer_axis_value120,
};
 
static void stop_repeat(Wayland *wayland)
{
    wayland->repeat.active = false;
}
 
static void start_repeat(Wayland *wayland, xkb_keycode_t key)
{
    if (!wayland->xkb.keymap || wayland->repeat.rate <= 0 || wayland->repeat.delayMilliseconds < 0)
        return;
    if (!xkb_keymap_key_repeats(wayland->xkb.keymap, key))
        return;
    wayland->repeat.active = true;
    wayland->repeat.key = key;
    wayland->repeat.nextMilliseconds = now_ms() + wayland->repeat.delayMilliseconds;
}
 
static void fire_repeat(Wayland *wayland)
{
    if (!wayland->repeat.active)
        return;
    long now = now_ms();
    while (wayland->repeat.active && now >= wayland->repeat.nextMilliseconds) {
        Event ev = { .kind = EVENT_KEY };
        input_fill_key(&wayland->xkb, &ev.key, wayland->repeat.key, true, true);
        ev.key.timeMilliseconds = (uint32_t)now;
        wayland->platform->modifiers = ev.key.modifiers;
        wl_push(wayland, ev);
        if (wayland->repeat.rate <= 0) {
            stop_repeat(wayland);
            break;
        }
        wayland->repeat.nextMilliseconds += 1000L / wayland->repeat.rate;
        if (wayland->repeat.nextMilliseconds <= now)
            wayland->repeat.nextMilliseconds = now + 1000L / wayland->repeat.rate;
    }
}
 
static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format,
                            int32_t fd, uint32_t size)
{
    Wayland *wayland = data;
    (void)keyboard;
    if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
        close(fd);
        return;
    }
    char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    if (map == MAP_FAILED)
        return;
    struct xkb_keymap *keymap = xkb_keymap_new_from_string(
        wayland->xkb.context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
    munmap(map, size);
    if (!keymap)
        return;
    struct xkb_state *state = xkb_state_new(keymap);
    if (!state) {
        xkb_keymap_unref(keymap);
        return;
    }
    inputXkbSetKeymap(&wayland->xkb, keymap, state);
    wayland->platform->modifiers = inputModifiers(&wayland->xkb);
}
 
static void keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial,
                           struct wl_surface *surface, struct wl_array *keys)
{
    Wayland *wayland = data;
    (void)keyboard;
    (void)serial;
    (void)surface;
    (void)keys;
    wayland->platform->focused = true;
    Event ev = { .kind = EVENT_FOCUS };
    ev.focus.focused = true;
    wl_push(wayland, ev);
}
 
static void keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial,
                           struct wl_surface *surface)
{
    Wayland *wayland = data;
    (void)keyboard;
    (void)serial;
    (void)surface;
    stop_repeat(wayland);
    wayland->platform->focused = false;
    Event ev = { .kind = EVENT_FOCUS };
    ev.focus.focused = false;
    wl_push(wayland, ev);
}
 
static void keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial,
                         uint32_t time, uint32_t key, uint32_t state)
{
    Wayland *wayland = data;
    (void)keyboard;
    (void)serial;
    xkb_keycode_t keycode = (xkb_keycode_t)(key + 8);
    bool pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED);
    if (wayland->xkb.state)
        xkb_state_update_key(wayland->xkb.state, keycode, pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
 
    Event ev = { .kind = EVENT_KEY };
    input_fill_key(&wayland->xkb, &ev.key, keycode, pressed, false);
    ev.key.timeMilliseconds = time;
    wayland->platform->modifiers = ev.key.modifiers;
    wl_push(wayland, ev);
 
    if (pressed)
        start_repeat(wayland, keycode);
    else if (wayland->repeat.active && wayland->repeat.key == keycode)
        stop_repeat(wayland);
}
 
static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial,
                               uint32_t mods_depressed, uint32_t mods_latched,
                               uint32_t mods_locked, uint32_t group)
{
    Wayland *wayland = data;
    (void)keyboard;
    (void)serial;
    if (wayland->xkb.state) {
        xkb_state_update_mask(wayland->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
        wayland->platform->modifiers = inputModifiers(&wayland->xkb);
    }
}
 
static void keyboard_repeat_info(void *data, struct wl_keyboard *keyboard,
                                 int32_t rate, int32_t delay)
{
    Wayland *wayland = data;
    (void)keyboard;
    wayland->repeat.rate = rate;
    wayland->repeat.delayMilliseconds = delay;
    if (rate <= 0)
        stop_repeat(wayland);
}
 
static const struct wl_keyboard_listener 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 caps)
{
    Wayland *wayland = data;
    if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wayland->pointer) {
        wayland->pointer = wl_seat_get_pointer(seat);
        wl_pointer_add_listener(wayland->pointer, &pointer_listener, wayland);
    } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wayland->pointer) {
        if (wl_pointer_get_version(wayland->pointer) >= 3)
            wl_pointer_release(wayland->pointer);
        else
            wl_pointer_destroy(wayland->pointer);
        wayland->pointer = NULL;
    }
 
    if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !wayland->keyboard) {
        wayland->keyboard = wl_seat_get_keyboard(seat);
        wl_keyboard_add_listener(wayland->keyboard, &keyboard_listener, wayland);
    } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && wayland->keyboard) {
        if (wl_keyboard_get_version(wayland->keyboard) >= 3)
            wl_keyboard_release(wayland->keyboard);
        else
            wl_keyboard_destroy(wayland->keyboard);
        wayland->keyboard = NULL;
        stop_repeat(wayland);
    }
}
 
static void seat_name(void *data, struct wl_seat *seat, const char *name)
{
    (void)data;
    (void)seat;
    (void)name;
}
 
static const struct wl_seat_listener seat_listener = {
    .capabilities = seat_capabilities,
    .name = seat_name,
};
 
static void output_geometry(void *data, struct wl_output *output, int32_t x, int32_t y,
                            int32_t pw, int32_t ph, int32_t subpixel, const char *make,
                            const char *model, int32_t transform)
{
    (void)data;
    (void)output;
    (void)x;
    (void)y;
    (void)pw;
    (void)ph;
    (void)subpixel;
    (void)make;
    (void)model;
    (void)transform;
}
 
static void output_mode(void *data, struct wl_output *output, uint32_t flags,
                        int32_t width, int32_t height, int32_t refresh)
{
    (void)data;
    (void)output;
    (void)flags;
    (void)width;
    (void)height;
    (void)refresh;
}
 
static void output_done(void *data, struct wl_output *output)
{
    (void)output;
    WlOutput *o = data;
    if (o->entered)
        maybe_resize(o->wayland);
}
 
static void output_scale(void *data, struct wl_output *output, int32_t factor)
{
    (void)output;
    WlOutput *o = data;
    o->scale = factor > 0 ? factor : 1;
}
 
static const struct wl_output_listener output_listener = {
    .geometry = output_geometry,
    .mode = output_mode,
    .done = output_done,
    .scale = output_scale,
};
 
static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *output)
{
    Wayland *wayland = data;
    (void)surface;
    for (int i = 0; i < wayland->outputCount; i++) {
        if (wayland->outputs[i].output == output) {
            wayland->outputs[i].entered = true;
            maybe_resize(wayland);
            return;
        }
    }
}
 
static void surface_leave(void *data, struct wl_surface *surface, struct wl_output *output)
{
    Wayland *wayland = data;
    (void)surface;
    for (int i = 0; i < wayland->outputCount; i++) {
        if (wayland->outputs[i].output == output) {
            wayland->outputs[i].entered = false;
            maybe_resize(wayland);
            return;
        }
    }
}
 
static const struct wl_surface_listener surface_listener = {
    .enter = surface_enter,
    .leave = surface_leave,
};
 
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_wm_base_listener xdg_wm_base_listener = {
    .ping = xdg_wm_base_ping,
};
 
static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
{
    Wayland *wayland = data;
    xdg_surface_ack_configure(xdg_surface, serial);
    maybe_resize(wayland);
    wayland->configured = true;
}
 
static const struct xdg_surface_listener xdg_surface_listener = {
    .configure = xdg_surface_configure,
};
 
static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
                                   int32_t width, int32_t height, struct wl_array *states)
{
    Wayland *wayland = data;
    (void)xdg_toplevel;
    (void)states;
    if (width > 0)
        wayland->pendingWidth = width;
    if (height > 0)
        wayland->pendingHeight = height;
}
 
static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
{
    Wayland *wayland = data;
    (void)xdg_toplevel;
    wayland->platform->running = false;
    wl_push(wayland, (Event){ .kind = EVENT_QUIT });
}
 
static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel,
                                          int32_t width, int32_t height)
{
    (void)data;
    (void)xdg_toplevel;
    (void)width;
    (void)height;
}
 
static void xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel,
                                         struct wl_array *capabilities)
{
    (void)data;
    (void)xdg_toplevel;
    (void)capabilities;
}
 
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
    .configure = xdg_toplevel_configure,
    .close = xdg_toplevel_close,
    .configure_bounds = xdg_toplevel_configure_bounds,
    .wm_capabilities = xdg_toplevel_wm_capabilities,
};
 
static void registry_global(void *data, struct wl_registry *registry, uint32_t name,
                            const char *interface, uint32_t version)
{
    Wayland *wayland = data;
    if (strcmp(interface, wl_compositor_interface.name) == 0) {
        uint32_t v = version >= 4 ? 4 : version;
        wayland->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, v);
    } else if (strcmp(interface, wl_shm_interface.name) == 0) {
        wayland->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
    } else if (strcmp(interface, wl_seat_interface.name) == 0) {
        if (!wayland->seat) {
            uint32_t v = version >= 8 ? 8 : version;
            wayland->seat = wl_registry_bind(registry, name, &wl_seat_interface, v);
            wl_seat_add_listener(wayland->seat, &seat_listener, wayland);
        }
    } else if (strcmp(interface, wl_output_interface.name) == 0) {
        if (wayland->outputCount < (int)(sizeof(wayland->outputs) / sizeof(wayland->outputs[0]))) {
            WlOutput *o = &wayland->outputs[wayland->outputCount++];
            uint32_t v = version >= 2 ? 2 : version;
            o->wayland = wayland;
            o->scale = 1;
            o->output = wl_registry_bind(registry, name, &wl_output_interface, v);
            wl_output_add_listener(o->output, &output_listener, o);
        }
    } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
        wayland->xdgWmBase = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
        xdg_wm_base_add_listener(wayland->xdgWmBase, &xdg_wm_base_listener, wayland);
    } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
        wayland->decorationManager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);
    }
}
 
static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
    (void)data;
    (void)registry;
    (void)name;
}
 
static const struct wl_registry_listener registry_listener = {
    .global = registry_global,
    .global_remove = registry_global_remove,
};
 
static int wayland_fd(Platform *platform)
{
    Wayland *wayland = platform->implementation;
    return wl_display_get_fd(wayland->display);
}
 
static int wayland_flush(Platform *platform)
{
    Wayland *wayland = platform->implementation;
    return wl_display_flush(wayland->display) < 0 ? -1 : 0;
}
 
static int wayland_dispatch(Platform *platform)
{
    Wayland *wayland = platform->implementation;
    if (wl_display_dispatch_pending(wayland->display) < 0)
        return -1;
    fire_repeat(wayland);
    return 0;
}
 
static int wayland_poll_events(Platform *platform, int timeoutMilliseconds)
{
    Wayland *wayland = platform->implementation;
    int timeout = timeoutMilliseconds;
    if (wayland->repeat.active) {
        long remain = wayland->repeat.nextMilliseconds - now_ms();
        if (remain < 0)
            remain = 0;
        if (timeout < 0 || remain < timeout)
            timeout = (int)remain;
    }
 
    while (wl_display_prepare_read(wayland->display) != 0) {
        if (wl_display_dispatch_pending(wayland->display) < 0)
            return -1;
    }
    wl_display_flush(wayland->display);
 
    struct pollfd fd = {
        .fd = wl_display_get_fd(wayland->display),
        .events = POLLIN,
    };
    int n = poll(&fd, 1, timeout);
    if (n < 0) {
        wl_display_cancel_read(wayland->display);
        return errno == EINTR ? 0 : -1;
    }
    if (n > 0) {
        if (wl_display_read_events(wayland->display) < 0)
            return -1;
    } else {
        wl_display_cancel_read(wayland->display);
    }
    if (wl_display_dispatch_pending(wayland->display) < 0)
        return -1;
    fire_repeat(wayland);
    return 0;
}
 
static int wayland_create_vk_surface(Platform *platform, void *vulkanInstance, void *outSurface)
{
    Wayland *wayland = platform->implementation;
    /* The ICD owns buffer scale / viewporter once it presents. */
    wl_surface_set_buffer_scale(wayland->surface, 1);
    VkWaylandSurfaceCreateInfoKHR ci = {
        .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
        .display = wayland->display,
        .surface = wayland->surface,
    };
    VkSurfaceKHR surface = VK_NULL_HANDLE;
    VkResult result = vkCreateWaylandSurfaceKHR((VkInstance)vulkanInstance, &ci, NULL, &surface);
    if (result != VK_SUCCESS) {
        fprintf(stderr, "wayland: vkCreateWaylandSurfaceKHR failed (%d)\n", (int)result);
        return -1;
    }
    *(VkSurfaceKHR *)outSurface = surface;
    return 0;
}
 
static int wayland_present(Platform *platform)
{
    Wayland *wayland = platform->implementation;
    if (platform->gpuPresent)
        return 0;
    if (!wayland->configured || !platform->pixels)
        return 0;
 
    WlBuf *b = NULL;
    for (int i = 0; i < WL_BUF_COUNT; i++) {
        if (!wayland->bufs[i].busy && wayland->bufs[i].buffer) {
            b = &wayland->bufs[i];
            break;
        }
    }
    if (!b) {
        wl_display_dispatch_pending(wayland->display);
        for (int i = 0; i < WL_BUF_COUNT; i++) {
            if (!wayland->bufs[i].busy && wayland->bufs[i].buffer) {
                b = &wayland->bufs[i];
                break;
            }
        }
    }
    if (!b)
        return 0;
 
    memcpy(b->data, platform->pixels, wayland->stagingSize);
    b->busy = true;
    wl_surface_set_buffer_scale(wayland->surface, platform->scale);
    wl_surface_attach(wayland->surface, b->buffer, 0, 0);
    if (wl_surface_get_version(wayland->surface) >= 4)
        wl_surface_damage_buffer(wayland->surface, 0, 0, platform->bufferWidth, platform->bufferHeight);
    else
        wl_surface_damage(wayland->surface, 0, 0, platform->width, platform->height);
    wl_surface_commit(wayland->surface);
    wl_display_flush(wayland->display);
    return 0;
}
 
static void wayland_destroy(Platform *platform)
{
    Wayland *wayland = platform->implementation;
    if (!wayland)
        return;
    stop_repeat(wayland);
    for (int i = 0; i < WL_BUF_COUNT; i++)
        destroy_wl_buf(&wayland->bufs[i]);
    free(wayland->staging);
    inputXkbFinish(&wayland->xkb);
    if (wayland->decoration)
        zxdg_toplevel_decoration_v1_destroy(wayland->decoration);
    if (wayland->decorationManager)
        zxdg_decoration_manager_v1_destroy(wayland->decorationManager);
    if (wayland->xdgToplevel)
        xdg_toplevel_destroy(wayland->xdgToplevel);
    if (wayland->xdgSurface)
        xdg_surface_destroy(wayland->xdgSurface);
    if (wayland->cursorSurface)
        wl_surface_destroy(wayland->cursorSurface);
    if (wayland->surface)
        wl_surface_destroy(wayland->surface);
    if (wayland->pointer) {
        if (wl_pointer_get_version(wayland->pointer) >= 3)
            wl_pointer_release(wayland->pointer);
        else
            wl_pointer_destroy(wayland->pointer);
    }
    if (wayland->keyboard) {
        if (wl_keyboard_get_version(wayland->keyboard) >= 3)
            wl_keyboard_release(wayland->keyboard);
        else
            wl_keyboard_destroy(wayland->keyboard);
    }
    if (wayland->seat) {
        if (wl_seat_get_version(wayland->seat) >= 5)
            wl_seat_release(wayland->seat);
        else
            wl_seat_destroy(wayland->seat);
    }
    for (int i = 0; i < wayland->outputCount; i++) {
        if (wayland->outputs[i].output)
            wl_output_destroy(wayland->outputs[i].output);
    }
    if (wayland->cursorTheme)
        wl_cursor_theme_destroy(wayland->cursorTheme);
    if (wayland->xdgWmBase)
        xdg_wm_base_destroy(wayland->xdgWmBase);
    if (wayland->compositor)
        wl_compositor_destroy(wayland->compositor);
    if (wayland->shm)
        wl_shm_destroy(wayland->shm);
    if (wayland->registry)
        wl_registry_destroy(wayland->registry);
    if (wayland->display)
        wl_display_disconnect(wayland->display);
    free(wayland);
    platform->implementation = NULL;
    platform->pixels = NULL;
}
 
int platformWaylandOpen(Platform *platform, const char *title, int width, int height)
{
    Wayland *wayland = calloc(1, sizeof(*wayland));
    if (!wayland)
        return -1;
    wayland->platform = platform;
    wayland->pendingWidth = width;
    wayland->pendingHeight = height;
    wayland->pendingScale = 1;
    wayland->repeat.rate = 25;
    wayland->repeat.delayMilliseconds = 600;
    platform->implementation = wayland;
    platform->backend = BACKEND_WAYLAND;
    platform->fileDescriptor = wayland_fd;
    platform->flush = wayland_flush;
    platform->dispatch = wayland_dispatch;
    platform->pollEvents = wayland_poll_events;
    platform->present = wayland_present;
    platform->destroy = wayland_destroy;
    platform->vulkanSurfaceExtension = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
    platform->createVulkanSurface = wayland_create_vk_surface;
 
    wayland->display = wl_display_connect(NULL);
    if (!wayland->display) {
        fprintf(stderr, "wayland: connect failed\n");
        wayland_destroy(platform);
        return -1;
    }
    if (inputXkbInit(&wayland->xkb) < 0) {
        wayland_destroy(platform);
        return -1;
    }
 
    wayland->registry = wl_display_get_registry(wayland->display);
    wl_registry_add_listener(wayland->registry, &registry_listener, wayland);
    wl_display_roundtrip(wayland->display);
    wl_display_roundtrip(wayland->display);
 
    if (!wayland->compositor || !wayland->shm || !wayland->xdgWmBase) {
        fprintf(stderr, "wayland: missing compositor, shm, or xdg_wm_base\n");
        wayland_destroy(platform);
        return -1;
    }
 
    wayland->surface = wl_compositor_create_surface(wayland->compositor);
    wl_surface_add_listener(wayland->surface, &surface_listener, wayland);
    wayland->cursorSurface = wl_compositor_create_surface(wayland->compositor);
    wayland->xdgSurface = xdg_wm_base_get_xdg_surface(wayland->xdgWmBase, wayland->surface);
    xdg_surface_add_listener(wayland->xdgSurface, &xdg_surface_listener, wayland);
    wayland->xdgToplevel = xdg_surface_get_toplevel(wayland->xdgSurface);
    xdg_toplevel_add_listener(wayland->xdgToplevel, &xdg_toplevel_listener, wayland);
    if (!title)
        title = "terminal";
    xdg_toplevel_set_title(wayland->xdgToplevel, title);
    xdg_toplevel_set_app_id(wayland->xdgToplevel, "terminal");
    xdg_toplevel_set_min_size(wayland->xdgToplevel, 160, 80);
    if (wayland->decorationManager) {
        wayland->decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(wayland->decorationManager, wayland->xdgToplevel);
        zxdg_toplevel_decoration_v1_set_mode(wayland->decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
    }
 
    if (apply_size(wayland, width, height, 1) < 0) {
        wayland_destroy(platform);
        return -1;
    }
 
    wl_surface_commit(wayland->surface);
    while (!wayland->configured) {
        if (wl_display_dispatch(wayland->display) < 0) {
            wayland_destroy(platform);
            return -1;
        }
    }
    platform->focused = true;
    return 0;
}
powered by btf.