| git.druid.rocks | index | militantprogrammer | astral_canvas | src/ | 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, ®istry_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;
}