diff options
Diffstat (limited to 'keyview.c')
| -rw-r--r-- | keyview.c | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/keyview.c b/keyview.c new file mode 100644 index 0000000..2424686 --- /dev/null +++ b/keyview.c @@ -0,0 +1,330 @@ +#include <errno.h> +#include <fcntl.h> +#define _GNU_SOURCE /* memfd_create */ +#include <sys/mman.h> +#undef _GNU_SOURCE +#include <unistd.h> +#include <png.h> +#include <linux/input.h> +#include <wayland-client.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/buffer.h> +#include <skalibs/djbunix.h> +#include <skalibs/iopause.h> +#include <skalibs/genalloc.h> +#include <skalibs/strerr.h> +#include "xdg-shell-client-protocol.h" + +#define WIDTH 472 +#define HEIGHT 96 + +static int +allocate_shm_file(size_t size) +{ + int fd = memfd_create("wl_shm", 0); + if (fd < 0) + return -1; + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + fd_close(fd); + return -1; + } + return fd; +} + +struct key_state { + int demo; + int dash; + int grab; + enum { JUMP2 = 1 << 0, JUMP1 = 1 << 1 } jump; + enum { UP = 1 << 0, LEFT = 1 << 1, DOWN = 1 << 2, RIGHT = 1 << 3 } arrows; +}; + +struct texture_info { + uint32_t width; + uint32_t height; + genalloc pixels; +}; + +struct client_state { + /* Globals */ + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct wl_shm *wl_shm; + struct wl_compositor *wl_compositor; + struct xdg_wm_base *xdg_wm_base; + /* Objects */ + struct wl_surface *wl_surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + /* Buffers */ + struct wl_buffer *buffers[2]; + uint32_t *buffers_data[2]; + int buffers_ready[2]; + /* Application state */ + struct key_state last_rendered_keys; + struct key_state keys; + struct texture_info texture; +}; + +static void +wl_buffer_release(void *data, struct wl_buffer *buffer) +{ + struct client_state *state = data; + for (size_t i = 0; i < sizeof(state->buffers) / sizeof(state->buffers[0]); ++i) { + if (buffer == state->buffers[i]) + state->buffers_ready[i] = 1; + } +} + +static const struct wl_buffer_listener wl_buffer_listener = { + .release = wl_buffer_release, +}; + +static void +copy_texture_square(uint32_t *dest, struct client_state *state, int index, int variant) +{ + uint32_t *pixels = genalloc_s(uint32_t, &state->texture.pixels); + for (uint32_t y = 0; y < 96; ++y) + memcpy(&dest[y * WIDTH + 94 * index], + &pixels[(96 * variant + y) * state->texture.width + 96 * index], + 96 * sizeof(uint32_t)); + wl_surface_damage_buffer(state->wl_surface, 94 * index, 0, 96, 96); +} + +static void +draw_frame(struct client_state *state, int refresh) +{ + struct wl_buffer *buffer = NULL; + uint32_t *data = NULL; + for (size_t i = 0; i < sizeof(state->buffers) / sizeof(state->buffers[0]); ++i) { + if (state->buffers_ready[i]) { + buffer = state->buffers[i]; + data = state->buffers_data[i]; + state->buffers_ready[i] = 0; + break; + } + } + if (buffer == NULL || data == NULL) + return; + + if (refresh || state->last_rendered_keys.demo != state->keys.demo) + copy_texture_square(data, state, 0, state->keys.demo); + if (refresh || state->last_rendered_keys.dash != state->keys.dash) + copy_texture_square(data, state, 1, state->keys.dash); + if (refresh || state->last_rendered_keys.grab != state->keys.grab) + copy_texture_square(data, state, 2, state->keys.grab); + if (refresh || state->last_rendered_keys.jump != state->keys.jump) + copy_texture_square(data, state, 3, state->keys.jump); + if (refresh || state->last_rendered_keys.arrows != state->keys.arrows) + copy_texture_square(data, state, 4, state->keys.arrows); + + memcpy(&state->last_rendered_keys, &state->keys, sizeof(state->keys)); + + wl_surface_attach(state->wl_surface, buffer, 0, 0); + wl_surface_commit(state->wl_surface); +} + +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + struct client_state *state = data; + xdg_surface_ack_configure(xdg_surface, serial); + draw_frame(state, 1); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + 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 +registry_global(void *data, struct wl_registry *wl_registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct client_state *state = data; + if (strcmp(interface, wl_shm_interface.name) == 0) { + state->wl_shm = wl_registry_bind(wl_registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, wl_compositor_interface.name) == 0) { + state->wl_compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + state->xdg_wm_base = wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state); + } +} + +static void +registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) +{ + /* This space deliberately left blank */ +} + +static const struct wl_registry_listener wl_registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove, +}; + +void +redraw(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + struct client_state *state = data; + draw_frame(state, 0); +} + +static const struct wl_callback_listener frame_listener = { .done = redraw }; + +void +process_keyevent(struct input_event *ev, struct key_state *keys) +{ + if (ev->type != EV_KEY) + return; + switch (ev->code) { + case KEY_V: + keys->demo = ev->value > 0; + break; + case KEY_X: + keys->dash = ev->value > 0; + break; + case KEY_Z: + keys->grab = ev->value > 0; + break; + case KEY_C: + keys->jump = ev->value ? keys->jump | JUMP1 : keys->jump & ~JUMP1; + break; + case KEY_SPACE: + keys->jump = ev->value ? keys->jump | JUMP2 : keys->jump & ~JUMP2; + break; + case KEY_LEFT: + keys->arrows = ev->value ? keys->arrows | LEFT : keys->arrows & ~LEFT; + break; + case KEY_RIGHT: + keys->arrows = ev->value ? keys->arrows | RIGHT : keys->arrows & ~RIGHT; + break; + case KEY_UP: + keys->arrows = ev->value ? keys->arrows | UP : keys->arrows & ~UP; + break; + case KEY_DOWN: + keys->arrows = ev->value ? keys->arrows | DOWN : keys->arrows & ~DOWN; + break; + } +} + +int +main(int argc, char *argv[]) +{ + PROG = "keyview"; + if (argc != 1) + strerr_dieusage(100, PROG); + + struct client_state state = { 0 }; + + { + png_image img = { .version = PNG_IMAGE_VERSION, .opaque = NULL }; + char *path = "texture.png"; + if (!png_image_begin_read_from_file(&img, path)) + strerr_diefu(111, "read PNG texture from ", path, ": ", img.message); + state.texture.width = img.width; + state.texture.height = img.height; + img.format = PNG_FORMAT_RGBA; + int32_t row_stride = PNG_IMAGE_ROW_STRIDE(img); + size_t bufsize = PNG_IMAGE_BUFFER_SIZE(img, row_stride); + state.texture.pixels = (genalloc)GENALLOC_ZERO; + if (!genalloc_ready_tuned(uint32_t, &state.texture.pixels, bufsize, 0, 0, 1)) + strerr_diefusys(111, "allocate pixel buffer"); + if (!png_image_finish_read(&img, 0, genalloc_s(uint32_t, &state.texture.pixels), row_stride, NULL)) + strerr_diefu(111, "read PNG texture from ", path, ": ", img.message); + genalloc_setlen(uint32_t, &state.texture.pixels, bufsize); + png_image_free(&img); + } + + state.wl_display = wl_display_connect(NULL); + if (state.wl_display == NULL) + strerr_diefusys(111, "connect to Wayland display"); + state.wl_registry = wl_display_get_registry(state.wl_display); + wl_registry_add_listener(state.wl_registry, &wl_registry_listener, &state); + wl_display_roundtrip(state.wl_display); + + state.wl_surface = wl_compositor_create_surface(state.wl_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); + xdg_toplevel_set_title(state.xdg_toplevel, PROG); + wl_surface_commit(state.wl_surface); + + { + size_t stride = WIDTH * 4; + size_t bufsize = stride * HEIGHT; + size_t size = bufsize * 2; + + int fd = allocate_shm_file(size); + if (fd < 0) + strerr_diefusys(111, "open shm file"); + + uint32_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + strerr_diefusys(111, "mmap shm file"); + + struct wl_shm_pool *pool = wl_shm_create_pool(state.wl_shm, fd, size); + for (size_t i = 0; i < sizeof(state.buffers) / sizeof(state.buffers[0]); ++i) { + state.buffers[i] = wl_shm_pool_create_buffer(pool, i * bufsize, + WIDTH, HEIGHT, stride, WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener(state.buffers[i], &wl_buffer_listener, &state); + state.buffers_data[i] = data + i * bufsize / sizeof(uint32_t); + state.buffers_ready[i] = 1; + } + wl_shm_pool_destroy(pool); + fd_close(fd); + } + + iopause_fd fds[] = { + { .fd = wl_display_get_fd(state.wl_display), .events = IOPAUSE_READ }, + { .fd = 0, .events = IOPAUSE_READ } + }; + if (ndelay_on(0) < 0) + strerr_diefusys(111, "set stdin as non-blocking"); + struct input_event event; + while (1) { + wl_display_dispatch_pending(state.wl_display); + if (sanitize_read(wl_display_flush(state.wl_display)) < 0 && errno != EPIPE) + strerr_diefusys(111, "flush Wayland queue"); + + if (iopause(fds, sizeof(fds) / sizeof(fds[0]), NULL, NULL) < 0) + strerr_diefusys(111, "poll in event loop"); + + if (fds[0].revents & IOPAUSE_READ) { + if (wl_display_dispatch(state.wl_display) < 0) + strerr_diefusys(111, "dispatch Wayland events"); + } + + if (fds[1].revents & IOPAUSE_READ) { + while (1) { + size_t w = 0; + int r = buffer_getall(buffer_0, (char *)&event, sizeof(event), &w); + if (!r) break; + if (r < 0) + strerr_diefusys(111, "read from stdin"); + process_keyevent(&event, &state.keys); + } + if (memcmp(&state.last_rendered_keys, &state.keys, sizeof(state.keys))) { + struct wl_callback *frame_callback = wl_surface_frame(state.wl_surface); + wl_callback_add_listener(frame_callback, &frame_listener, &state); + wl_surface_commit(state.wl_surface); + } + } + } + return 0; +} + |
