Files
waymini/waymini.c
T
2026-07-03 01:38:23 -07:00

356 lines
11 KiB
C

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/memfd.h>
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
enum fmt { FMT_RGBA32 = 4 };
struct shm_state {
int fd;
size_t size;
void *map;
struct wl_shm *shm_iface;
struct wl_buffer *wl_buffer;
};
struct window_state {
struct wl_compositor *compositor;
struct wl_surface *surface;
struct xdg_wm_base *wm_base;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
uint32_t width, height;
uint32_t bpp;
uint32_t stride;
int configured;
};
struct input_state {
struct wl_seat *seat;
struct wl_keyboard *keyboard;
void (*on_keycode)(void *user, uint32_t keycode, uint32_t state);
void *user;
};
struct app {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_shm *shm_iface;
struct xdg_wm_base *wm_base;
struct wl_event_queue *queue;
struct shm_state shm;
struct window_state win;
struct input_state in;
};
static void on_xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
struct app *app = data;
xdg_surface_ack_configure(xdg_surface, serial);
app->win.configured = 1;
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = on_xdg_surface_configure,
};
static void on_toplevel_configure(void *data, struct xdg_toplevel *toplevel,
int32_t width, int32_t height,
struct wl_array *states) {
(void)data; (void)toplevel; (void)width; (void)height; (void)states;
}
static void on_toplevel_close(void *data, struct xdg_toplevel *toplevel) {
(void)data; (void)toplevel;
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = on_toplevel_configure,
.close = on_toplevel_close,
};
static void on_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) {
(void)data;
xdg_wm_base_pong(wm_base, serial);
}
static const struct xdg_wm_base_listener wm_base_listener = {
.ping = on_wm_base_ping,
};
static void on_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {
(void)keyboard;
struct input_state *in = data;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
return;
}
// Minimal: drain keymap then close.
char *buf = malloc(size);
if (buf) {
size_t off = 0;
while (off < size) {
ssize_t r = read(fd, buf + off, size - off);
if (r <= 0) break;
off += (size_t)r;
}
free(buf);
} else {
// If alloc fails, still drain.
(void)read(fd, buf, 0);
}
close(fd);
(void)in;
}
static void on_keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state) {
(void)keyboard; (void)serial; (void)time;
struct input_state *in = data;
if (in->on_keycode) in->on_keycode(in->user, key, state);
}
static void on_keyboard_enter(void *data, struct wl_keyboard *keyboard,
uint32_t serial, struct wl_surface *surface,
struct wl_array *keys) {
(void)data; (void)keyboard; (void)serial; (void)surface; (void)keys;
}
static void on_keyboard_leave(void *data, struct wl_keyboard *keyboard,
uint32_t serial, struct wl_surface *surface) {
(void)data; (void)keyboard; (void)serial; (void)surface;
}
static void on_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) {
(void)data; (void)keyboard; (void)serial;
(void)mods_depressed; (void)mods_latched; (void)mods_locked; (void)group;
}
static void on_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 keyboard_listener = {
.keymap = on_keymap,
.enter = on_keyboard_enter,
.leave = on_keyboard_leave,
.key = on_keyboard_key,
.modifiers = on_keyboard_modifiers,
.repeat_info = on_keyboard_repeat_info,
};
static void on_seat_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) {
struct input_state *in = data;
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !in->keyboard) {
in->seat = seat;
in->keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(in->keyboard, &keyboard_listener, in);
}
}
static void on_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 = on_seat_capabilities,
.name = on_seat_name,
};
static void on_registry_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) {
struct app *app = data;
(void)registry;
if (strcmp(interface, "wl_compositor") == 0) {
app->compositor = wl_registry_bind(app->registry, name, &wl_compositor_interface, 4);
} else if (strcmp(interface, "wl_shm") == 0) {
app->shm_iface = wl_registry_bind(app->registry, name, &wl_shm_interface, 1);
app->shm.shm_iface = app->shm_iface;
} else if (strcmp(interface, "xdg_wm_base") == 0) {
app->wm_base = wl_registry_bind(app->registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(app->wm_base, &wm_base_listener, app);
} else if (strcmp(interface, "wl_seat") == 0) {
struct wl_seat *seat = wl_registry_bind(app->registry, name, &wl_seat_interface, version);
wl_seat_add_listener(seat, &seat_listener, &app->in);
}
}
static void on_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 = on_registry_global,
.global_remove = on_registry_global_remove,
};
static void waymini_destroy(struct app *app);
static int create_shm_buffer(struct app *app) {
struct shm_state *s = &app->shm;
struct window_state *w = &app->win;
s->size = (size_t)w->stride * (size_t)w->height;
s->fd = memfd_create("waymini-shm", MFD_CLOEXEC);
if (s->fd < 0) { perror("memfd_create"); return -1; }
if (ftruncate(s->fd, (off_t)s->size) < 0) { perror("ftruncate"); return -1; }
s->map = mmap(NULL, s->size, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, 0);
if (s->map == MAP_FAILED) { perror("mmap"); return -1; }
struct wl_buffer *buf;
struct wl_shm_pool *pool = wl_shm_create_pool(s->shm_iface, s->fd, (int)s->size);
buf = wl_shm_pool_create_buffer(pool, 0, w->width, w->height, (int)w->stride, WL_SHM_FORMAT_XRGB8888);
wl_shm_pool_destroy(pool);
if (!buf) { fprintf(stderr, "wl_shm_pool_create_buffer failed\n"); return -1; }
s->wl_buffer = buf;
return 0;
}
static struct app *waymini_create(uint32_t width, uint32_t height,
void (*on_keycode)(void*, uint32_t, uint32_t),
void *user) {
struct app *app = calloc(1, sizeof(*app));
if (!app) return NULL;
app->display = wl_display_connect(NULL);
if (!app->display) { fprintf(stderr, "wl_display_connect failed\n"); free(app); return NULL; }
app->registry = wl_display_get_registry(app->display);
wl_registry_add_listener(app->registry, &registry_listener, app);
wl_display_roundtrip(app->display);
if (!app->compositor || !app->shm_iface || !app->wm_base) {
fprintf(stderr, "missing globals\n");
waymini_destroy(app);
return NULL;
}
app->win.compositor = app->compositor;
app->win.surface = wl_compositor_create_surface(app->compositor);
app->win.xdg_surface = xdg_wm_base_get_xdg_surface(app->wm_base, app->win.surface);
xdg_surface_add_listener(app->win.xdg_surface, &xdg_surface_listener, app);
app->win.xdg_toplevel = xdg_surface_get_toplevel(app->win.xdg_surface);
xdg_toplevel_add_listener(app->win.xdg_toplevel, &xdg_toplevel_listener, app);
xdg_toplevel_set_title(app->win.xdg_toplevel, "waymini");
xdg_toplevel_set_app_id(app->win.xdg_toplevel, "waymini");
app->in.on_keycode = on_keycode;
app->in.user = user;
app->win.width = width;
app->win.height = height;
app->win.bpp = 4;
app->win.stride = width * app->win.bpp;
/* Initial commit without a buffer, as required by xdg-shell. */
wl_surface_commit(app->win.surface);
/* Wait for the first xdg_surface.configure. */
while (!app->win.configured) {
if (wl_display_dispatch(app->display) < 0) {
fprintf(stderr, "dispatch failed waiting for configure\n");
waymini_destroy(app);
return NULL;
}
}
if (create_shm_buffer(app) < 0) {
fprintf(stderr, "create_shm_buffer failed\n");
waymini_destroy(app);
return NULL;
}
return app;
}
static void waymini_present(struct app *app) {
if (!app->win.configured) return;
wl_surface_attach(app->win.surface, app->shm.wl_buffer, 0, 0);
wl_surface_commit(app->win.surface);
}
static void waymini_destroy(struct app *app) {
if (!app) return;
if (app->in.keyboard) wl_keyboard_destroy(app->in.keyboard);
if (app->in.seat) wl_seat_destroy(app->in.seat);
if (app->shm.wl_buffer) wl_buffer_destroy(app->shm.wl_buffer);
if (app->shm.map && app->shm.size) munmap(app->shm.map, app->shm.size);
if (app->shm.fd >= 0) close(app->shm.fd);
if (app->win.xdg_toplevel) xdg_toplevel_destroy(app->win.xdg_toplevel);
if (app->win.xdg_surface) xdg_surface_destroy(app->win.xdg_surface);
if (app->win.surface) wl_surface_destroy(app->win.surface);
if (app->wm_base) xdg_wm_base_destroy(app->wm_base);
if (app->compositor) wl_compositor_destroy(app->compositor);
if (app->shm_iface) wl_shm_destroy(app->shm_iface);
if (app->registry) wl_registry_destroy(app->registry);
if (app->display) wl_display_disconnect(app->display);
free(app);
}
static void on_keycode(void *user, uint32_t keycode, uint32_t state) {
(void)user;
printf("key=%u state=%u\n", keycode, state);
fflush(stdout);
}
int main() {
uint32_t w = 800, h = 480;
struct app *app = waymini_create(w, h, on_keycode, NULL);
if (!app) return 1;
// Fill RGBA-ish bytes, but we're attaching as XRGB8888; color order is compositor-dependent.
uint8_t *p = (uint8_t*)app->shm.map;
for (uint32_t y = 0; y < h; y++) {
for (uint32_t x = 0; x < w; x++) {
size_t i = (size_t)y * app->win.stride + (size_t)x * 4;
p[i + 0] = 0x20;
p[i + 1] = (uint8_t)(y * 255 / h);
p[i + 2] = (uint8_t)(x * 255 / w);
p[i + 3] = 0xff;
}
}
waymini_present(app);
while (wl_display_dispatch(app->display) != -1) {
/* Optionally re-present here if content changes. */
}
waymini_destroy(app);
return 0;
}